mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-13 11:50:48 +01:00
Split three more linux_configuration giants
raspberry_pi_nextcloud.sh 1276 -> 163, root_bl9000.sh 1116 -> 211, lookup_docs.sh 942 -> 165. Sixteen new libs. lookup_python was one 300-line function; its keyword-to-anchor mapping became python_keyword_doc, emitting result and desc tab-separated so the caller keeps its control flow. bl9000_mtkclient had cd X; work; cd - with three return paths. install_mtkclient became a subshell; extract_boot_with_mtkclient could not, since it exports BOOT_IMG - so the venv check moved before the cd, the cd is guarded, and the restores use a saved $PWD. Four more unguarded cd calls fixed, and a third instance of the lib chaining bug flattened. lookup_docs.sh had no SCRIPT_DIR at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
5bacfa3883
commit
7a74804786
@ -0,0 +1,18 @@
|
||||
{
|
||||
"title": "Split three more very large linux_configuration scripts",
|
||||
"objective": "Bring raspberry_pi_nextcloud.sh (1276), root_bl9000.sh (1116) and lookup_docs.sh (942) under the 250-line cap, fixing the cd handling the splits expose rather than suppressing it.",
|
||||
"acceptance_criteria": [
|
||||
"All three entry scripts and all sixteen new libs are at most 250 lines.",
|
||||
"The pre-commit shellcheck hook passes on every touched file, with no new suppression and no disable directive.",
|
||||
"No lib sources a sibling: each entry script sources every lib directly, in dependency order.",
|
||||
"The stubbed run reports every moved function as defined, for all three scripts.",
|
||||
"Every SC2164 and SC2103 the split exposes is fixed at the source. Where a function exports a variable to its caller it must not become a subshell; guard the cd and restore the previous directory instead.",
|
||||
"No script is executed. These flash SD cards, root a phone over USB and configure a web server."
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Executing any flash, root or configure step.",
|
||||
"check_and_enable_services.sh and the other deferred scripts, whose seams cross shared mutable state.",
|
||||
"Changing what any script does when run, beyond the cd guards."
|
||||
],
|
||||
"verifier": "meta/scripts/verify_shell_split.sh per script, plus shellcheck over every lib in isolation, plus a grep confirming no lib sources a sibling, plus pre-commit run --files and check_file_length.sh."
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
{
|
||||
"intent": "Split three more very large linux_configuration scripts under the cap, and fix the directory handling the splits exposed.",
|
||||
"scope": [
|
||||
"single_use/features/raspberry_pi_nextcloud.sh (1276 -> 163) + 7 libs",
|
||||
"single_use/utils/root_bl9000.sh (1116 -> 211) + 6 libs",
|
||||
"single_use/utils/lookup_docs.sh (942 -> 165) + 6 libs",
|
||||
"Non-goal: no script executed"
|
||||
],
|
||||
"changes": [
|
||||
"Sixteen new libs across the three scripts. raspberry_pi_nextcloud and root_bl9000 each needed a second carve after the first pass left a lib over the cap.",
|
||||
"lookup_docs.sh's lookup_python was a single 300-line function. Extracted the keyword-to-anchor mapping into python_keyword_doc, which emits result and desc tab-separated so the caller keeps its own control flow - the block only ever assigned those two variables.",
|
||||
"Fixed four more unguarded cd calls in the Nextcloud libs, each before an occ command or a download.",
|
||||
"bl9000_mtkclient.sh had the cd X; work; cd - pattern with three separate return paths. install_mtkclient became a subshell. extract_boot_with_mtkclient could NOT: it exports BOOT_IMG to its caller, so a subshell would swallow it. Instead the venv check moved to before the cd (so that early return needs no cd back), the cd is guarded, and the restores use a saved $PWD rather than cd -.",
|
||||
"Flattened two chained source blocks. The root_bl9000 usage split had absorbed the entry's whole source block into a lib, and the docs split had done the same. Both entries now source every lib directly.",
|
||||
"lookup_docs.sh had no SCRIPT_DIR at all - it needed one adding before its new source block."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "meta/scripts/verify_shell_split.sh per script",
|
||||
"result": "fail then pass",
|
||||
"evidence": "Three rounds of failures, each a real defect: SC2164 on the unguarded cd calls, SC1091 from the chained source blocks, and then '/lib/docs_python_keywords.sh: No such file' because SCRIPT_DIR was undefined. All three report 'libs sourced, functions defined' now."
|
||||
},
|
||||
{
|
||||
"command": "shellcheck over every lib in isolation",
|
||||
"result": "pass",
|
||||
"evidence": "Clean, including SC2103, with no disable directive anywhere."
|
||||
},
|
||||
{
|
||||
"command": "grep -l 'source \"$SCRIPT_DIR/lib/' over all libs in the touched directories",
|
||||
"result": "pass",
|
||||
"evidence": "No matches - no lib sources a sibling. This is the third occurrence of the chaining bug, so it is now checked explicitly rather than noticed."
|
||||
},
|
||||
{
|
||||
"command": "bash ~/utils/scripts/check_file_length.sh --all",
|
||||
"result": "pass",
|
||||
"evidence": "None of the three entry scripts or sixteen libs is listed."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"None of the three was run: they flash SD cards, put a phone into BROM mode over USB, and configure Apache and MariaDB. The cd fixes change failure paths only.",
|
||||
"extract_boot_with_mtkclient still uses cd rather than a subshell, deliberately, because it exports BOOT_IMG. The restore is now unconditional on both exit paths, but a future edit adding a third return must restore too - a subshell would make that impossible to get wrong, at the cost of the exported variable."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit; the three scripts return to their previous sizes and the sixteen libs disappear.",
|
||||
"After rollback, re-run bash -n and shellcheck on the restored scripts."
|
||||
]
|
||||
}
|
||||
179
linux_configuration/scripts/single_use/features/lib/rpi_nc_ca.sh
Executable file
179
linux_configuration/scripts/single_use/features/lib/rpi_nc_ca.sh
Executable file
@ -0,0 +1,179 @@
|
||||
#!/bin/bash
|
||||
# CA certificate installation.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
# =============================================================================
|
||||
# Install CA Certificate on Client
|
||||
# =============================================================================
|
||||
|
||||
phase_install_ca() {
|
||||
log_info "=== Installing Nextcloud CA Certificate ==="
|
||||
|
||||
if [[ -z $PI_PASSWORD ]]; then
|
||||
die "PI_PASSWORD not set. Run this after running install-remote or flash."
|
||||
fi
|
||||
|
||||
local pi_ip
|
||||
pi_ip=$(discover_raspberry_pi)
|
||||
|
||||
if [[ -z $pi_ip ]]; then
|
||||
die "Failed to discover Raspberry Pi"
|
||||
fi
|
||||
|
||||
log_info "Downloading CA certificate from Pi..."
|
||||
|
||||
local ca_file="/tmp/nextcloud-ca.crt"
|
||||
|
||||
# Use SSH with sudo to cat the file (since it's in a protected directory)
|
||||
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no \
|
||||
"${PI_USER}@${pi_ip}" "echo '$PI_PASSWORD' | sudo -S cat /etc/ssl/nextcloud/ca.crt" >"$ca_file" 2>/dev/null
|
||||
|
||||
if [[ ! -f $ca_file ]] || [[ ! -s $ca_file ]]; then
|
||||
die "Failed to download CA certificate"
|
||||
fi
|
||||
|
||||
log_success "CA certificate downloaded to: $ca_file"
|
||||
|
||||
# Detect OS and install appropriately
|
||||
if [[ -f /etc/arch-release ]]; then
|
||||
log_info "Detected Arch Linux - installing CA..."
|
||||
sudo cp "$ca_file" /etc/ca-certificates/trust-source/anchors/nextcloud-ca.crt
|
||||
sudo trust extract-compat
|
||||
log_success "CA installed in system trust store"
|
||||
|
||||
elif [[ -f /etc/debian_version ]]; then
|
||||
log_info "Detected Debian/Ubuntu - installing CA..."
|
||||
sudo cp "$ca_file" /usr/local/share/ca-certificates/nextcloud-ca.crt
|
||||
sudo update-ca-certificates
|
||||
log_success "CA installed in system trust store"
|
||||
|
||||
elif [[ -f /etc/redhat-release ]]; then
|
||||
log_info "Detected RHEL/Fedora - installing CA..."
|
||||
sudo cp "$ca_file" /etc/pki/ca-trust/source/anchors/nextcloud-ca.crt
|
||||
sudo update-ca-trust
|
||||
log_success "CA installed in system trust store"
|
||||
|
||||
elif [[ "$(uname)" == "Darwin" ]]; then
|
||||
log_info "Detected macOS - installing CA..."
|
||||
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$ca_file"
|
||||
log_success "CA installed in system keychain"
|
||||
|
||||
else
|
||||
log_warning "Unknown OS - please install CA manually from: $ca_file"
|
||||
fi
|
||||
|
||||
# Install in browser certificate stores
|
||||
log_info "Installing CA in browser certificate stores..."
|
||||
|
||||
# Chrome/Chromium (uses NSS)
|
||||
if [[ -d ~/.pki/nssdb ]] || command -v certutil &>/dev/null; then
|
||||
mkdir -p ~/.pki/nssdb
|
||||
if ! certutil -d sql:~/.pki/nssdb -L 2>/dev/null | grep -q "Nextcloud"; then
|
||||
# Initialize NSS db if needed
|
||||
certutil -d sql:~/.pki/nssdb -N --empty-password 2>/dev/null || true
|
||||
if certutil -d sql:~/.pki/nssdb -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2>/dev/null; then
|
||||
log_success "CA installed in Chrome/Chromium"
|
||||
else
|
||||
log_warning "Could not install in Chrome/Chromium NSS db"
|
||||
fi
|
||||
else
|
||||
log_info "CA already installed in Chrome/Chromium"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Firefox (has its own profile NSS databases)
|
||||
if [[ -d ~/.mozilla/firefox ]]; then
|
||||
local installed=0
|
||||
for profile_dir in ~/.mozilla/firefox/*.default* ~/.mozilla/firefox/*.esr*; do
|
||||
if [[ -d $profile_dir ]]; then
|
||||
if ! certutil -d sql:"$profile_dir" -L 2>/dev/null | grep -q "Nextcloud"; then
|
||||
certutil -d sql:"$profile_dir" -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2>/dev/null &&
|
||||
installed=1
|
||||
else
|
||||
installed=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [[ $installed -eq 1 ]]; then
|
||||
log_success "CA installed in Firefox"
|
||||
else
|
||||
log_warning "Could not install in Firefox - you may need to import manually"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add hostname to /etc/hosts if not present
|
||||
if ! grep -q "$PI_HOSTNAME" /etc/hosts 2>/dev/null; then
|
||||
log_info "Adding $PI_HOSTNAME to /etc/hosts..."
|
||||
echo "$pi_ip $PI_HOSTNAME ${PI_HOSTNAME}.local" | sudo tee -a /etc/hosts >/dev/null
|
||||
log_success "Added $PI_HOSTNAME to /etc/hosts"
|
||||
else
|
||||
log_info "$PI_HOSTNAME already in /etc/hosts"
|
||||
fi
|
||||
|
||||
# Verify
|
||||
log_info "Verifying HTTPS connection..."
|
||||
if curl -s --max-time 5 "https://$PI_HOSTNAME/status.php" 2>/dev/null | grep -q "installed"; then
|
||||
log_success "HTTPS connection verified - no certificate warnings!"
|
||||
else
|
||||
log_warning "Could not verify HTTPS - you may need to restart your browser"
|
||||
fi
|
||||
|
||||
log_success "========================================"
|
||||
log_success "CA Certificate installed!"
|
||||
log_success "========================================"
|
||||
echo
|
||||
log_info "Access Nextcloud at: https://$PI_HOSTNAME"
|
||||
log_info "Your browser should now trust the certificate without warnings."
|
||||
echo
|
||||
log_info "For other devices (phones, tablets, other computers):"
|
||||
log_info " Download: https://$PI_HOSTNAME/ca/nextcloud-ca.crt"
|
||||
log_info " Then install the certificate in your device's trust store."
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Main
|
||||
# =============================================================================
|
||||
|
||||
show_help() {
|
||||
cat <<'EOF'
|
||||
Nextcloud Installation Script for Raspberry Pi
|
||||
|
||||
Usage: ./raspberry_pi_nextcloud.sh <command>
|
||||
|
||||
Commands:
|
||||
install-remote Install Nextcloud via SSH from your laptop (recommended)
|
||||
setup-ssl-remote Setup Let's Encrypt SSL with DuckDNS (auto-trusted on all devices)
|
||||
install-ca Install self-signed CA on this machine (alternative to setup-ssl)
|
||||
configure Configure Pi system (run on Pi)
|
||||
install-local Install Nextcloud (run on Pi)
|
||||
fix Fix common Nextcloud issues (run on Pi)
|
||||
setup-ssl Setup Let's Encrypt SSL (run on Pi)
|
||||
help Show this help message
|
||||
|
||||
The script will:
|
||||
1. Configure the Raspberry Pi system (SSH hardening, firewall, etc.)
|
||||
2. Install Apache, PHP, MariaDB, Redis
|
||||
3. Download and install Nextcloud
|
||||
4. Configure caching, background jobs, and security
|
||||
|
||||
For HTTPS trusted on ALL devices automatically:
|
||||
./raspberry_pi_nextcloud.sh install-remote
|
||||
./raspberry_pi_nextcloud.sh setup-ssl-remote
|
||||
|
||||
This uses DuckDNS (free) + Let's Encrypt for real trusted certificates.
|
||||
Go to https://www.duckdns.org/ to get your free domain first.
|
||||
|
||||
For self-signed certificates (requires manual CA install on each device):
|
||||
./raspberry_pi_nextcloud.sh install-remote
|
||||
./raspberry_pi_nextcloud.sh install-ca
|
||||
|
||||
For local installation (on Pi):
|
||||
sudo ./raspberry_pi_nextcloud.sh configure
|
||||
sudo ./raspberry_pi_nextcloud.sh install-local
|
||||
sudo ./raspberry_pi_nextcloud.sh fix
|
||||
sudo ./raspberry_pi_nextcloud.sh setup-ssl
|
||||
EOF
|
||||
}
|
||||
119
linux_configuration/scripts/single_use/features/lib/rpi_nc_deps.sh
Executable file
119
linux_configuration/scripts/single_use/features/lib/rpi_nc_deps.sh
Executable file
@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# Dependency install, apt lock wait and Pi discovery.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
wait_for_apt_lock() {
|
||||
local max_wait=600
|
||||
local waited=0
|
||||
|
||||
while fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; do
|
||||
if [[ $waited -eq 0 ]]; then
|
||||
log_info "Waiting for other apt/dpkg processes to finish..."
|
||||
pgrep -a 'apt|dpkg' | head -5 >&2 || true
|
||||
fi
|
||||
sleep 5
|
||||
waited=$((waited + 5))
|
||||
if [[ $waited -ge $max_wait ]]; then
|
||||
die "Timeout waiting for apt lock after ${max_wait}s"
|
||||
fi
|
||||
if [[ $((waited % 30)) -eq 0 ]]; then
|
||||
log_info "Still waiting... (${waited}s elapsed)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $waited -gt 0 ]]; then
|
||||
log_success "Apt lock acquired after ${waited}s"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Network Discovery (for remote installation)
|
||||
# =============================================================================
|
||||
|
||||
ensure_dependencies() {
|
||||
local missing_packages=()
|
||||
|
||||
if ! command -v nmap &>/dev/null; then
|
||||
missing_packages+=("nmap")
|
||||
fi
|
||||
|
||||
if ! command -v sshpass &>/dev/null; then
|
||||
missing_packages+=("sshpass")
|
||||
fi
|
||||
|
||||
if [[ ${#missing_packages[@]} -gt 0 ]]; then
|
||||
log_info "Installing missing packages: ${missing_packages[*]}"
|
||||
|
||||
if command -v pacman &>/dev/null; then
|
||||
sudo pacman -S --noconfirm "${missing_packages[@]}"
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"
|
||||
elif command -v dnf &>/dev/null; then
|
||||
sudo dnf install -y "${missing_packages[@]}"
|
||||
else
|
||||
die "Could not detect package manager. Please install manually: ${missing_packages[*]}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
discover_raspberry_pi() {
|
||||
log_info "Auto-discovering Raspberry Pi on local network..."
|
||||
|
||||
ensure_dependencies
|
||||
|
||||
local my_ip
|
||||
my_ip=$(ip -4 addr show | grep -oP '(?<=inet\s)(?!127\.)\d+(\.\d+){3}' | head -1)
|
||||
local gateway
|
||||
gateway=$(ip route | grep default | awk '{print $3}' | head -1)
|
||||
local network="${gateway%.*}.0/24"
|
||||
|
||||
log_info "Local IP: $my_ip, Network: $network"
|
||||
log_info "Scanning for Raspberry Pi (hostname: $PI_HOSTNAME)..."
|
||||
|
||||
local pi_ip=""
|
||||
|
||||
# Try resolving hostname directly
|
||||
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
|
||||
if [[ -z $pi_ip ]]; then
|
||||
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
|
||||
fi
|
||||
|
||||
if [[ -n $pi_ip ]]; then
|
||||
log_success "Found Pi by hostname: $pi_ip"
|
||||
echo "$pi_ip"
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "Hostname resolution failed, scanning network..."
|
||||
nmap -sn -T4 "$network" &>/dev/null || true
|
||||
|
||||
local ssh_hosts
|
||||
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) || true
|
||||
|
||||
if [[ -z $ssh_hosts ]]; then
|
||||
die "No SSH-enabled devices found. Is the Pi connected and booted?"
|
||||
fi
|
||||
|
||||
log_info "Found SSH-enabled devices: $(echo "$ssh_hosts" | tr '\n' ' ')"
|
||||
|
||||
for ip in $ssh_hosts; do
|
||||
log_info "Trying $ip with user '$PI_USER'..."
|
||||
|
||||
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "hostname" 2>/dev/null | grep -qi "$PI_HOSTNAME"; then
|
||||
log_success "Found Raspberry Pi at $ip"
|
||||
echo "$ip"
|
||||
return
|
||||
fi
|
||||
|
||||
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "echo ok" 2>/dev/null | grep -q "ok"; then
|
||||
log_success "Found device responding to Pi credentials at $ip"
|
||||
echo "$ip"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
die "Could not find Raspberry Pi on network."
|
||||
}
|
||||
233
linux_configuration/scripts/single_use/features/lib/rpi_nc_fixes.sh
Executable file
233
linux_configuration/scripts/single_use/features/lib/rpi_nc_fixes.sh
Executable file
@ -0,0 +1,233 @@
|
||||
#!/bin/bash
|
||||
# The post-install fix-up phase.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
# =============================================================================
|
||||
# Fix Nextcloud Issues
|
||||
# =============================================================================
|
||||
|
||||
# shellcheck disable=SC2120 # Function does not use positional args
|
||||
phase_fix_issues() {
|
||||
check_root
|
||||
|
||||
log_info "=== Fixing Nextcloud Issues ==="
|
||||
|
||||
cd /var/www/nextcloud || { log_error "Nextcloud is not installed at /var/www/nextcloud"; return 1; }
|
||||
|
||||
# 1. Fix background jobs (cron not running properly)
|
||||
log_info "Fixing background jobs..."
|
||||
|
||||
# Ensure cron is set as background job method
|
||||
sudo -u www-data php occ background:cron
|
||||
|
||||
# Ensure cron job exists and is correct
|
||||
(
|
||||
crontab -u www-data -l 2>/dev/null | grep -v "cron.php"
|
||||
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php"
|
||||
) | crontab -u www-data -
|
||||
|
||||
# Run cron manually now to reset the timer
|
||||
log_info "Running cron job manually..."
|
||||
sudo -u www-data php /var/www/nextcloud/cron.php
|
||||
|
||||
log_success "Background jobs configured"
|
||||
|
||||
# 2. Setup HTTPS with proper CA-signed certificate
|
||||
log_info "Setting up HTTPS with trusted CA..."
|
||||
|
||||
local pi_ip
|
||||
pi_ip=$(hostname -I | awk '{print $1}')
|
||||
|
||||
local ssl_dir="/etc/ssl/nextcloud"
|
||||
mkdir -p "$ssl_dir"
|
||||
chmod 700 "$ssl_dir"
|
||||
|
||||
# Generate CA if it doesn't exist
|
||||
if [[ ! -f "$ssl_dir/ca.crt" ]]; then
|
||||
log_info "Creating Certificate Authority (CA)..."
|
||||
|
||||
# Generate CA private key
|
||||
openssl genrsa -out "$ssl_dir/ca.key" 4096
|
||||
chmod 600 "$ssl_dir/ca.key"
|
||||
|
||||
# Generate CA certificate (valid for 10 years)
|
||||
openssl req -x509 -new -nodes -key "$ssl_dir/ca.key" \
|
||||
-sha256 -days 3650 \
|
||||
-out "$ssl_dir/ca.crt" \
|
||||
-subj "/C=PL/ST=Home/L=Local/O=Nextcloud Home CA/OU=Certificate Authority/CN=Nextcloud Home CA"
|
||||
|
||||
log_success "CA created: $ssl_dir/ca.crt"
|
||||
fi
|
||||
|
||||
# Generate server certificate signed by our CA
|
||||
local regenerate="${1:-}"
|
||||
if [[ ! -f "$ssl_dir/server.crt" ]] || [[ $regenerate == "--regenerate" ]]; then
|
||||
log_info "Generating server certificate signed by CA..."
|
||||
|
||||
# Generate server private key
|
||||
openssl genrsa -out "$ssl_dir/server.key" 2048
|
||||
chmod 600 "$ssl_dir/server.key"
|
||||
|
||||
# Create certificate signing request (CSR)
|
||||
openssl req -new -key "$ssl_dir/server.key" \
|
||||
-out "$ssl_dir/server.csr" \
|
||||
-subj "/C=PL/ST=Home/L=Local/O=Nextcloud/OU=Server/CN=$PI_HOSTNAME"
|
||||
|
||||
# Create extension file for SAN (Subject Alternative Names)
|
||||
# This allows the certificate to be valid for hostname, IP, and .local
|
||||
cat >"$ssl_dir/server.ext" <<EXTEOF
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints=CA:FALSE
|
||||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = $PI_HOSTNAME
|
||||
DNS.2 = ${PI_HOSTNAME}.local
|
||||
DNS.3 = localhost
|
||||
IP.1 = $pi_ip
|
||||
IP.2 = 127.0.0.1
|
||||
EXTEOF
|
||||
|
||||
# Sign the certificate with our CA (valid for 2 years)
|
||||
openssl x509 -req -in "$ssl_dir/server.csr" \
|
||||
-CA "$ssl_dir/ca.crt" \
|
||||
-CAkey "$ssl_dir/ca.key" \
|
||||
-CAcreateserial \
|
||||
-out "$ssl_dir/server.crt" \
|
||||
-days 730 \
|
||||
-sha256 \
|
||||
-extfile "$ssl_dir/server.ext"
|
||||
|
||||
rm -f "$ssl_dir/server.csr" "$ssl_dir/server.ext"
|
||||
|
||||
log_success "Server certificate created and signed by CA"
|
||||
fi
|
||||
|
||||
# Copy CA to web-accessible location for easy download
|
||||
mkdir -p /var/www/nextcloud/ca
|
||||
cp "$ssl_dir/ca.crt" /var/www/nextcloud/ca/nextcloud-ca.crt
|
||||
chown -R www-data:www-data /var/www/nextcloud/ca
|
||||
|
||||
log_info "CA certificate available at: https://$PI_HOSTNAME/ca/nextcloud-ca.crt"
|
||||
|
||||
# Create HTTPS Apache config
|
||||
cat >/etc/apache2/sites-available/nextcloud-ssl.conf <<EOF
|
||||
<VirtualHost *:443>
|
||||
ServerAdmin admin@localhost
|
||||
DocumentRoot /var/www/nextcloud
|
||||
ServerName $PI_HOSTNAME
|
||||
ServerAlias ${PI_HOSTNAME}.local $pi_ip
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile $ssl_dir/server.crt
|
||||
SSLCertificateKeyFile $ssl_dir/server.key
|
||||
|
||||
<Directory /var/www/nextcloud/>
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
Options FollowSymLinks MultiViews
|
||||
|
||||
<IfModule mod_dav.c>
|
||||
Dav off
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
# Security headers
|
||||
Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "no-referrer"
|
||||
|
||||
ErrorLog \${APACHE_LOG_DIR}/nextcloud_ssl_error.log
|
||||
CustomLog \${APACHE_LOG_DIR}/nextcloud_ssl_access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
<VirtualHost *:80>
|
||||
ServerName $PI_HOSTNAME
|
||||
ServerAlias $pi_ip
|
||||
Redirect permanent / https://$PI_HOSTNAME/
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
a2enmod ssl
|
||||
a2enmod headers
|
||||
a2ensite nextcloud-ssl
|
||||
|
||||
# Update Nextcloud config for HTTPS
|
||||
sudo -u www-data php occ config:system:set overwrite.cli.url --value="https://$PI_HOSTNAME"
|
||||
sudo -u www-data php occ config:system:set overwriteprotocol --value="https"
|
||||
|
||||
systemctl restart apache2
|
||||
|
||||
log_success "HTTPS configured with CA-signed certificate"
|
||||
|
||||
# 3. Run mimetype migrations
|
||||
log_info "Running mimetype migrations..."
|
||||
sudo -u www-data php occ maintenance:repair --include-expensive
|
||||
log_success "Mimetype migrations complete"
|
||||
|
||||
# 4. Add missing database indices
|
||||
log_info "Adding missing database indices..."
|
||||
sudo -u www-data php occ db:add-missing-indices
|
||||
log_success "Database indices added"
|
||||
|
||||
# 5. Install ImageMagick SVG support
|
||||
log_info "Installing ImageMagick SVG support..."
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y libmagickcore-6.q16-6-extra
|
||||
|
||||
# Enable SVG in ImageMagick policy
|
||||
local policy_file="/etc/ImageMagick-6/policy.xml"
|
||||
if [[ -f $policy_file ]]; then
|
||||
# Remove SVG restrictions if present
|
||||
sed -i 's/<policy domain="coder" rights="none" pattern="SVG" \/>/<policy domain="coder" rights="read|write" pattern="SVG" \/>/' "$policy_file"
|
||||
# If no SVG policy exists, add one allowing it
|
||||
if ! grep -q 'pattern="SVG"' "$policy_file"; then
|
||||
sed -i '/<policymap>/a\ <policy domain="coder" rights="read|write" pattern="SVG" \/>' "$policy_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
systemctl restart apache2
|
||||
log_success "ImageMagick SVG support configured"
|
||||
|
||||
# 6. Set up basic SMTP (placeholder - user needs to configure actual mail server)
|
||||
log_info "Note: Email server not configured - please configure in Nextcloud admin settings"
|
||||
|
||||
# 7. Clear any remaining warnings
|
||||
log_info "Clearing Nextcloud caches..."
|
||||
sudo -u www-data php occ maintenance:repair
|
||||
sudo -u www-data php occ files:scan --all
|
||||
|
||||
# 8. Verify all fixes
|
||||
log_info "Verifying fixes..."
|
||||
|
||||
# Run cron again to update last run time
|
||||
sudo -u www-data php /var/www/nextcloud/cron.php
|
||||
|
||||
log_success "========================================"
|
||||
log_success "Nextcloud issues fixed!"
|
||||
log_success "========================================"
|
||||
echo
|
||||
log_info "Summary of changes:"
|
||||
log_info " ✓ Background jobs (cron) configured and running"
|
||||
log_info " ✓ HTTPS enabled with CA-signed certificate"
|
||||
log_info " ✓ Strict-Transport-Security header added"
|
||||
log_info " ✓ Mimetype migrations completed"
|
||||
log_info " ✓ Missing database indices added"
|
||||
log_info " ✓ ImageMagick SVG support installed"
|
||||
echo
|
||||
log_info "Current certificate: self-signed CA (requires manual install on devices)"
|
||||
log_info " - Run: $0 install-ca (on your laptop)"
|
||||
log_info " - Or download: https://$PI_HOSTNAME/ca/nextcloud-ca.crt"
|
||||
echo
|
||||
log_info "For auto-trusted HTTPS on ALL devices (recommended):"
|
||||
log_info " 1. Get free domain at https://www.duckdns.org/"
|
||||
log_info " 2. Run: $0 setup-ssl"
|
||||
echo
|
||||
log_info "Access Nextcloud at: https://$PI_HOSTNAME"
|
||||
}
|
||||
228
linux_configuration/scripts/single_use/features/lib/rpi_nc_install.sh
Executable file
228
linux_configuration/scripts/single_use/features/lib/rpi_nc_install.sh
Executable file
@ -0,0 +1,228 @@
|
||||
#!/bin/bash
|
||||
# The Nextcloud install phase.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
# =============================================================================
|
||||
# Nextcloud Installation Phase
|
||||
# =============================================================================
|
||||
|
||||
phase_install_nextcloud() {
|
||||
check_root
|
||||
|
||||
log_info "=== Installing Nextcloud ==="
|
||||
|
||||
wait_for_apt_lock
|
||||
|
||||
log_info "Installing Apache, PHP, MariaDB, and dependencies..."
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
apache2 \
|
||||
mariadb-server \
|
||||
php \
|
||||
php-gd \
|
||||
php-json \
|
||||
php-mysql \
|
||||
php-curl \
|
||||
php-mbstring \
|
||||
php-intl \
|
||||
php-imagick \
|
||||
php-xml \
|
||||
php-zip \
|
||||
php-bz2 \
|
||||
php-bcmath \
|
||||
php-gmp \
|
||||
php-apcu \
|
||||
php-redis \
|
||||
php-ldap \
|
||||
libapache2-mod-php \
|
||||
redis-server \
|
||||
certbot \
|
||||
python3-certbot-apache \
|
||||
imagemagick \
|
||||
libmagickcore-6.q16-6-extra
|
||||
|
||||
log_success "Packages installed"
|
||||
|
||||
# Configure MariaDB
|
||||
log_info "Configuring MariaDB..."
|
||||
|
||||
local db_password
|
||||
db_password=$(generate_password 32)
|
||||
|
||||
mysql -u root <<EOF
|
||||
CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
CREATE USER IF NOT EXISTS 'nextcloud'@'localhost' IDENTIFIED BY '${db_password}';
|
||||
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
|
||||
echo "$db_password" >/root/.nextcloud_db_password
|
||||
chmod 600 /root/.nextcloud_db_password
|
||||
log_success "MariaDB configured"
|
||||
|
||||
# Download Nextcloud
|
||||
log_info "Downloading Nextcloud..."
|
||||
|
||||
cd /tmp || { log_error "Cannot enter /tmp"; return 1; }
|
||||
if [[ ! -f nextcloud.zip ]]; then
|
||||
wget -q --show-progress "https://download.nextcloud.com/server/releases/latest.zip" -O nextcloud.zip >&2
|
||||
fi
|
||||
|
||||
rm -rf /var/www/nextcloud
|
||||
unzip -q nextcloud.zip -d /var/www/
|
||||
chown -R www-data:www-data /var/www/nextcloud
|
||||
|
||||
log_success "Nextcloud downloaded and extracted"
|
||||
|
||||
# Configure Apache
|
||||
log_info "Configuring Apache..."
|
||||
|
||||
cat >/etc/apache2/sites-available/nextcloud.conf <<'EOF'
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin admin@localhost
|
||||
DocumentRoot /var/www/nextcloud
|
||||
|
||||
<Directory /var/www/nextcloud/>
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
Options FollowSymLinks MultiViews
|
||||
|
||||
<IfModule mod_dav.c>
|
||||
Dav off
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/nextcloud_error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/nextcloud_access.log combined
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
a2enmod rewrite
|
||||
a2enmod headers
|
||||
a2enmod env
|
||||
a2enmod dir
|
||||
a2enmod mime
|
||||
a2enmod ssl
|
||||
a2dissite 000-default
|
||||
a2ensite nextcloud
|
||||
|
||||
systemctl restart apache2
|
||||
|
||||
log_success "Apache configured"
|
||||
|
||||
# Configure PHP
|
||||
log_info "Configuring PHP..."
|
||||
|
||||
local php_version
|
||||
php_version=$(php -v | head -1 | grep -oP '\d+\.\d+')
|
||||
|
||||
local php_ini="/etc/php/${php_version}/apache2/php.ini"
|
||||
|
||||
sed -i 's/memory_limit = .*/memory_limit = 512M/' "$php_ini"
|
||||
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 16G/' "$php_ini"
|
||||
sed -i 's/post_max_size = .*/post_max_size = 16G/' "$php_ini"
|
||||
sed -i 's/max_execution_time = .*/max_execution_time = 3600/' "$php_ini"
|
||||
sed -i 's/max_input_time = .*/max_input_time = 3600/' "$php_ini"
|
||||
sed -i 's/;date.timezone =.*/date.timezone = Europe\/Warsaw/' "$php_ini"
|
||||
|
||||
if ! grep -q "opcache.interned_strings_buffer" "$php_ini"; then
|
||||
cat >>"$php_ini" <<'EOF'
|
||||
|
||||
; Nextcloud optimizations
|
||||
opcache.enable=1
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.memory_consumption=128
|
||||
opcache.save_comments=1
|
||||
opcache.revalidate_freq=1
|
||||
|
||||
; APCu configuration
|
||||
apc.enable_cli=1
|
||||
EOF
|
||||
fi
|
||||
|
||||
systemctl restart apache2
|
||||
|
||||
log_success "PHP configured"
|
||||
|
||||
# Configure Redis
|
||||
log_info "Configuring Redis..."
|
||||
|
||||
systemctl enable redis-server
|
||||
systemctl start redis-server
|
||||
|
||||
log_success "Redis configured"
|
||||
|
||||
# Install Nextcloud
|
||||
log_info "Installing Nextcloud..."
|
||||
|
||||
auto_generate_nextcloud_password
|
||||
|
||||
local pi_ip
|
||||
pi_ip=$(hostname -I | awk '{print $1}')
|
||||
|
||||
cd /var/www/nextcloud || { log_error "Nextcloud is not installed at /var/www/nextcloud"; return 1; }
|
||||
sudo -u www-data php occ maintenance:install \
|
||||
--database "mysql" \
|
||||
--database-name "nextcloud" \
|
||||
--database-user "nextcloud" \
|
||||
--database-pass "$db_password" \
|
||||
--admin-user "$NEXTCLOUD_ADMIN_USER" \
|
||||
--admin-pass "$NEXTCLOUD_ADMIN_PASSWORD" \
|
||||
--data-dir "$NEXTCLOUD_DATA_DIR"
|
||||
|
||||
# Configure trusted domains
|
||||
sudo -u www-data php occ config:system:set trusted_domains 1 --value="$pi_ip"
|
||||
sudo -u www-data php occ config:system:set trusted_domains 2 --value="$PI_HOSTNAME"
|
||||
sudo -u www-data php occ config:system:set trusted_domains 3 --value="${PI_HOSTNAME}.local"
|
||||
|
||||
# Configure caching
|
||||
sudo -u www-data php occ config:system:set memcache.local --value='\OC\Memcache\APCu'
|
||||
sudo -u www-data php occ config:system:set memcache.distributed --value='\OC\Memcache\Redis'
|
||||
sudo -u www-data php occ config:system:set memcache.locking --value='\OC\Memcache\Redis'
|
||||
sudo -u www-data php occ config:system:set redis host --value='localhost'
|
||||
sudo -u www-data php occ config:system:set redis port --value=6379 --type=integer
|
||||
|
||||
# Set default phone region
|
||||
sudo -u www-data php occ config:system:set default_phone_region --value='PL'
|
||||
|
||||
# Set maintenance window
|
||||
sudo -u www-data php occ config:system:set maintenance_window_start --value=1 --type=integer
|
||||
|
||||
log_success "Nextcloud installed"
|
||||
|
||||
# Setup background jobs
|
||||
log_info "Setting up Nextcloud background jobs..."
|
||||
|
||||
sudo -u www-data php occ background:cron
|
||||
|
||||
# Add cron job
|
||||
(
|
||||
crontab -u www-data -l 2>/dev/null || true
|
||||
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php"
|
||||
) | sort -u | crontab -u www-data -
|
||||
|
||||
log_success "Cron jobs configured"
|
||||
|
||||
# Verify installation
|
||||
log_info "Verifying Nextcloud installation..."
|
||||
|
||||
if sudo -u www-data php occ status | grep -q "installed: true"; then
|
||||
log_success "Nextcloud is responding!"
|
||||
sudo -u www-data php occ status
|
||||
else
|
||||
log_warning "Nextcloud may not be fully configured"
|
||||
fi
|
||||
|
||||
save_config
|
||||
|
||||
log_success "========================================"
|
||||
log_success "Nextcloud installation complete!"
|
||||
log_success "========================================"
|
||||
log_info "Access Nextcloud at: http://$pi_ip"
|
||||
log_info "Admin user: $NEXTCLOUD_ADMIN_USER"
|
||||
log_info "Admin password: $NEXTCLOUD_ADMIN_PASSWORD"
|
||||
log_info "Database password saved at: /root/.nextcloud_db_password"
|
||||
}
|
||||
197
linux_configuration/scripts/single_use/features/lib/rpi_nc_ssl.sh
Executable file
197
linux_configuration/scripts/single_use/features/lib/rpi_nc_ssl.sh
Executable file
@ -0,0 +1,197 @@
|
||||
#!/bin/bash
|
||||
# SSL setup, local and remote.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
# =============================================================================
|
||||
# Setup Let's Encrypt SSL with DuckDNS
|
||||
# =============================================================================
|
||||
|
||||
phase_setup_ssl() {
|
||||
check_root
|
||||
|
||||
log_info "=== Setting up Let's Encrypt SSL with DuckDNS ==="
|
||||
|
||||
# Check if DuckDNS is configured
|
||||
if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]]; then
|
||||
echo
|
||||
log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain."
|
||||
log_info "1. Go to https://www.duckdns.org/ and sign in with Google/GitHub/etc."
|
||||
log_info "2. Create a subdomain (e.g., 'myhomecloud' for myhomecloud.duckdns.org)"
|
||||
log_info "3. Copy your token from the DuckDNS page"
|
||||
echo
|
||||
|
||||
read -r -p "Enter your DuckDNS subdomain (without .duckdns.org): " DUCKDNS_DOMAIN
|
||||
read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN
|
||||
read -r -p "Enter your email (for Let's Encrypt notifications): " LETSENCRYPT_EMAIL
|
||||
|
||||
if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]] || [[ -z $LETSENCRYPT_EMAIL ]]; then
|
||||
die "All fields are required"
|
||||
fi
|
||||
fi
|
||||
|
||||
local full_domain="${DUCKDNS_DOMAIN}.duckdns.org"
|
||||
local pi_local_ip
|
||||
pi_local_ip=$(hostname -I | awk '{print $1}')
|
||||
|
||||
# Get public IP for DuckDNS (Let's Encrypt needs external access)
|
||||
local public_ip
|
||||
public_ip=$(curl -s https://api.ipify.org) || public_ip=$(curl -s https://ifconfig.me) || true
|
||||
|
||||
log_info "Domain: $full_domain"
|
||||
log_info "Pi local IP: $pi_local_ip"
|
||||
log_info "Public IP: $public_ip"
|
||||
|
||||
echo
|
||||
log_warning "=== IMPORTANT: Port Forwarding Required ==="
|
||||
log_warning "For Let's Encrypt to work, you MUST forward ports on your router:"
|
||||
log_warning " - Forward port 80 (HTTP) to $pi_local_ip"
|
||||
log_warning " - Forward port 443 (HTTPS) to $pi_local_ip"
|
||||
log_warning ""
|
||||
log_warning "Go to your router admin page (usually http://192.168.1.1)"
|
||||
log_warning "and set up port forwarding before continuing."
|
||||
echo
|
||||
read -r -p "Have you set up port forwarding? (yes/no): " port_forward_done
|
||||
|
||||
if [[ $port_forward_done != "yes" ]]; then
|
||||
log_info "Please set up port forwarding and run this command again."
|
||||
log_info "Without port forwarding, Let's Encrypt cannot verify your domain."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update DuckDNS to point to PUBLIC IP (not local IP)
|
||||
log_info "Updating DuckDNS to point to public IP $public_ip..."
|
||||
local duckdns_response
|
||||
# When ip= is empty, DuckDNS auto-detects the public IP
|
||||
duckdns_response=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=")
|
||||
|
||||
if [[ $duckdns_response != "OK" ]]; then
|
||||
die "Failed to update DuckDNS: $duckdns_response"
|
||||
fi
|
||||
log_success "DuckDNS updated to public IP"
|
||||
|
||||
# Set up automatic DuckDNS updates (cron) - auto-detect public IP
|
||||
log_info "Setting up automatic DuckDNS IP updates..."
|
||||
mkdir -p /opt/duckdns
|
||||
cat >/opt/duckdns/duck.sh <<DUCKEOF
|
||||
#!/bin/bash
|
||||
echo url="https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=" | curl -k -o /opt/duckdns/duck.log -K -
|
||||
DUCKEOF
|
||||
chmod 700 /opt/duckdns/duck.sh
|
||||
|
||||
# Add cron job for DuckDNS update every 5 minutes
|
||||
(crontab -l 2>/dev/null || true) | grep -v "duckdns" | {
|
||||
cat
|
||||
echo "*/5 * * * * /opt/duckdns/duck.sh >/dev/null 2>&1"
|
||||
} | crontab -
|
||||
|
||||
log_success "DuckDNS auto-update configured"
|
||||
|
||||
# Wait for DNS propagation
|
||||
log_info "Waiting for DNS propagation (this may take a minute)..."
|
||||
local dns_ip=""
|
||||
local attempts=0
|
||||
while [[ $dns_ip != "$public_ip" ]] && [[ $attempts -lt 12 ]]; do
|
||||
sleep 5
|
||||
dns_ip=$(dig +short "$full_domain" 2>/dev/null | tail -1) || true
|
||||
attempts=$((attempts + 1))
|
||||
log_info " DNS lookup: $dns_ip (expecting $public_ip, attempt $attempts/12)"
|
||||
done
|
||||
|
||||
if [[ $dns_ip != "$public_ip" ]]; then
|
||||
log_warning "DNS may not have propagated yet. Continuing anyway..."
|
||||
else
|
||||
log_success "DNS verified: $full_domain -> $public_ip"
|
||||
fi
|
||||
|
||||
# Install certbot if not present
|
||||
if ! command -v certbot &>/dev/null; then
|
||||
log_info "Installing certbot..."
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y certbot python3-certbot-apache
|
||||
fi
|
||||
|
||||
# Get Let's Encrypt certificate
|
||||
log_info "Obtaining Let's Encrypt certificate..."
|
||||
|
||||
# First update Apache config with the new domain
|
||||
cat >/etc/apache2/sites-available/nextcloud-ssl.conf <<EOF
|
||||
<VirtualHost *:443>
|
||||
ServerAdmin ${LETSENCRYPT_EMAIL}
|
||||
DocumentRoot /var/www/nextcloud
|
||||
ServerName ${full_domain}
|
||||
|
||||
SSLEngine on
|
||||
# Certbot will update these paths
|
||||
SSLCertificateFile /etc/ssl/nextcloud/server.crt
|
||||
SSLCertificateKeyFile /etc/ssl/nextcloud/server.key
|
||||
|
||||
<Directory /var/www/nextcloud/>
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
Options FollowSymLinks MultiViews
|
||||
|
||||
<IfModule mod_dav.c>
|
||||
Dav off
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
# Security headers
|
||||
Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "no-referrer"
|
||||
|
||||
ErrorLog \${APACHE_LOG_DIR}/nextcloud_ssl_error.log
|
||||
CustomLog \${APACHE_LOG_DIR}/nextcloud_ssl_access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName ${full_domain}
|
||||
Redirect permanent / https://${full_domain}/
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
systemctl reload apache2
|
||||
|
||||
# Run certbot
|
||||
certbot --apache -d "$full_domain" --non-interactive --agree-tos --email "$LETSENCRYPT_EMAIL" --redirect
|
||||
|
||||
log_success "Let's Encrypt certificate obtained!"
|
||||
|
||||
# Update Nextcloud trusted domains
|
||||
log_info "Updating Nextcloud configuration..."
|
||||
cd /var/www/nextcloud || { log_error "Nextcloud is not installed at /var/www/nextcloud"; return 1; }
|
||||
sudo -u www-data php occ config:system:set trusted_domains 0 --value="$full_domain"
|
||||
sudo -u www-data php occ config:system:set overwrite.cli.url --value="https://$full_domain"
|
||||
sudo -u www-data php occ config:system:set overwriteprotocol --value="https"
|
||||
sudo -u www-data php occ config:system:set overwritehost --value="$full_domain"
|
||||
|
||||
# Keep local access working
|
||||
sudo -u www-data php occ config:system:set trusted_domains 1 --value="$pi_local_ip"
|
||||
sudo -u www-data php occ config:system:set trusted_domains 2 --value="$PI_HOSTNAME"
|
||||
sudo -u www-data php occ config:system:set trusted_domains 3 --value="${PI_HOSTNAME}.local"
|
||||
|
||||
log_success "Nextcloud configured for $full_domain"
|
||||
|
||||
# Set up auto-renewal
|
||||
log_info "Setting up automatic certificate renewal..."
|
||||
systemctl enable certbot.timer
|
||||
systemctl start certbot.timer
|
||||
|
||||
log_success "========================================"
|
||||
log_success "Let's Encrypt SSL configured!"
|
||||
log_success "========================================"
|
||||
echo
|
||||
log_info "Your Nextcloud is now accessible at:"
|
||||
log_info " https://$full_domain (from anywhere on the internet)"
|
||||
log_info " https://$pi_local_ip (from your local network)"
|
||||
echo
|
||||
log_info "This certificate is trusted by ALL browsers and devices automatically!"
|
||||
log_info "No manual certificate installation required."
|
||||
echo
|
||||
log_info "Certificate auto-renewal is enabled."
|
||||
log_info "DuckDNS IP auto-update is enabled."
|
||||
}
|
||||
111
linux_configuration/scripts/single_use/features/lib/rpi_nc_ssl_remote.sh
Executable file
111
linux_configuration/scripts/single_use/features/lib/rpi_nc_ssl_remote.sh
Executable file
@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# Remote SSL setup and the remote install phase.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep rpi_nc_ssl.sh
|
||||
# under the 250-line cap. Sourced rather than run, so it inherits the
|
||||
# caller's strict mode and the variables defined above the source line.
|
||||
|
||||
phase_setup_ssl_remote() {
|
||||
log_info "=== Setting up Let's Encrypt SSL via SSH ==="
|
||||
|
||||
if [[ -z $PI_PASSWORD ]]; then
|
||||
die "PI_PASSWORD not set. Run install-remote first."
|
||||
fi
|
||||
|
||||
local pi_ip
|
||||
pi_ip=$(discover_raspberry_pi)
|
||||
|
||||
if [[ -z $pi_ip ]]; then
|
||||
die "Failed to discover Raspberry Pi"
|
||||
fi
|
||||
|
||||
# Get DuckDNS credentials if not set
|
||||
if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]]; then
|
||||
echo
|
||||
log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain."
|
||||
log_info "1. Go to https://www.duckdns.org/ and sign in"
|
||||
log_info "2. Create a subdomain (e.g., 'myhomecloud')"
|
||||
log_info "3. Copy your token"
|
||||
echo
|
||||
|
||||
read -r -p "Enter your DuckDNS subdomain (without .duckdns.org): " DUCKDNS_DOMAIN
|
||||
read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN
|
||||
read -r -p "Enter your email (for Let's Encrypt): " LETSENCRYPT_EMAIL
|
||||
fi
|
||||
|
||||
save_config
|
||||
|
||||
log_info "Copying script to Pi..."
|
||||
sshpass -p "$PI_PASSWORD" scp -o StrictHostKeyChecking=no "$0" "${PI_USER}@${pi_ip}:/tmp/raspberry_pi_nextcloud.sh"
|
||||
|
||||
log_info "Running SSL setup on Pi..."
|
||||
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no "${PI_USER}@${pi_ip}" \
|
||||
"echo '$PI_PASSWORD' | sudo -S DUCKDNS_DOMAIN='$DUCKDNS_DOMAIN' DUCKDNS_TOKEN='$DUCKDNS_TOKEN' LETSENCRYPT_EMAIL='$LETSENCRYPT_EMAIL' bash /tmp/raspberry_pi_nextcloud.sh setup-ssl"
|
||||
|
||||
local full_domain="${DUCKDNS_DOMAIN}.duckdns.org"
|
||||
|
||||
log_success "========================================"
|
||||
log_success "SSL setup complete!"
|
||||
log_success "========================================"
|
||||
echo
|
||||
log_info "Access your Nextcloud at: https://$full_domain"
|
||||
log_info "This works on ALL devices without certificate warnings!"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Remote Installation
|
||||
# =============================================================================
|
||||
|
||||
phase_install_remote() {
|
||||
log_info "=== Installing Nextcloud via SSH ==="
|
||||
|
||||
if [[ -z $PI_PASSWORD ]]; then
|
||||
die "PI_PASSWORD not set. Did you run flash script first?"
|
||||
fi
|
||||
|
||||
local pi_ip
|
||||
pi_ip=$(discover_raspberry_pi)
|
||||
|
||||
if [[ -z $pi_ip ]]; then
|
||||
die "Failed to discover Raspberry Pi"
|
||||
fi
|
||||
|
||||
log_info "Using Raspberry Pi at: $pi_ip"
|
||||
|
||||
# Remove old host key if present
|
||||
ssh-keygen -R "$pi_ip" 2>/dev/null || true
|
||||
|
||||
log_info "Copying script to Pi..."
|
||||
sshpass -p "$PI_PASSWORD" scp -o StrictHostKeyChecking=no "$0" "${PI_USER}@${pi_ip}:/tmp/raspberry_pi_nextcloud.sh"
|
||||
|
||||
log_info "Running system configuration on Pi..."
|
||||
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no "${PI_USER}@${pi_ip}" \
|
||||
"echo '$PI_PASSWORD' | sudo -S bash /tmp/raspberry_pi_nextcloud.sh configure"
|
||||
|
||||
log_info "Installing Nextcloud on Pi..."
|
||||
auto_generate_nextcloud_password
|
||||
save_config
|
||||
|
||||
log_success "Nextcloud admin user: $NEXTCLOUD_ADMIN_USER"
|
||||
log_success "Nextcloud admin password: $NEXTCLOUD_ADMIN_PASSWORD"
|
||||
|
||||
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no "${PI_USER}@${pi_ip}" \
|
||||
"echo '$PI_PASSWORD' | sudo -S NEXTCLOUD_ADMIN_PASSWORD='$NEXTCLOUD_ADMIN_PASSWORD' NEXTCLOUD_ADMIN_USER='$NEXTCLOUD_ADMIN_USER' bash /tmp/raspberry_pi_nextcloud.sh install-local"
|
||||
|
||||
log_info "Fixing Nextcloud issues..."
|
||||
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no "${PI_USER}@${pi_ip}" \
|
||||
"echo '$PI_PASSWORD' | sudo -S bash /tmp/raspberry_pi_nextcloud.sh fix"
|
||||
|
||||
log_success "========================================"
|
||||
log_success "Remote Nextcloud installation complete!"
|
||||
log_success "========================================"
|
||||
echo
|
||||
log_info "=== Access Information ==="
|
||||
log_info "Nextcloud URL: https://$pi_ip"
|
||||
log_info "Admin user: $NEXTCLOUD_ADMIN_USER"
|
||||
log_info "Admin password: $NEXTCLOUD_ADMIN_PASSWORD"
|
||||
log_info "All credentials saved in: $CONFIG_FILE"
|
||||
echo
|
||||
log_info "=== Trust the certificate ==="
|
||||
log_info "Run: $0 install-ca"
|
||||
}
|
||||
104
linux_configuration/scripts/single_use/features/lib/rpi_nc_system.sh
Executable file
104
linux_configuration/scripts/single_use/features/lib/rpi_nc_system.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# System configuration phase.
|
||||
#
|
||||
# Sourced by raspberry_pi_nextcloud.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
# =============================================================================
|
||||
# System Configuration Phase
|
||||
# =============================================================================
|
||||
|
||||
phase_configure_system() {
|
||||
check_root
|
||||
|
||||
log_info "=== Configuring Raspberry Pi System ==="
|
||||
|
||||
wait_for_apt_lock
|
||||
|
||||
log_info "Fixing any broken packages..."
|
||||
DEBIAN_FRONTEND=noninteractive dpkg --configure -a --force-confdef --force-confold || true
|
||||
|
||||
log_info "Updating system packages..."
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade -y
|
||||
|
||||
log_info "Setting timezone to $PI_TIMEZONE..."
|
||||
timedatectl set-timezone "$PI_TIMEZONE"
|
||||
|
||||
log_info "Configuring locale..."
|
||||
sed -i "s/^# *$PI_LOCALE/$PI_LOCALE/" /etc/locale.gen
|
||||
locale-gen
|
||||
update-locale LANG="$PI_LOCALE"
|
||||
|
||||
log_info "Hardening SSH configuration..."
|
||||
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
|
||||
|
||||
cat >>/etc/ssh/sshd_config.d/hardening.conf <<'EOF'
|
||||
# Security hardening
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication yes
|
||||
PubkeyAuthentication yes
|
||||
X11Forwarding no
|
||||
MaxAuthTries 3
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
EOF
|
||||
|
||||
systemctl restart sshd
|
||||
|
||||
log_info "Installing useful packages..."
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
vim \
|
||||
htop \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
ufw \
|
||||
fail2ban \
|
||||
unattended-upgrades
|
||||
|
||||
log_info "Configuring firewall..."
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow ssh
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw --force enable
|
||||
|
||||
log_info "Configuring fail2ban..."
|
||||
cat >/etc/fail2ban/jail.local <<'EOF'
|
||||
[DEFAULT]
|
||||
bantime = 1h
|
||||
findtime = 10m
|
||||
maxretry = 5
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = 3
|
||||
EOF
|
||||
|
||||
systemctl enable fail2ban
|
||||
systemctl restart fail2ban
|
||||
|
||||
log_info "Enabling automatic security updates..."
|
||||
cat >/etc/apt/apt.conf.d/50unattended-upgrades <<'EOF'
|
||||
Unattended-Upgrade::Origins-Pattern {
|
||||
"origin=Debian,codename=${distro_codename},label=Debian-Security";
|
||||
"origin=Raspbian,codename=${distro_codename},label=Raspbian";
|
||||
};
|
||||
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
EOF
|
||||
|
||||
cat >/etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
EOF
|
||||
|
||||
log_success "System configuration complete!"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
165
linux_configuration/scripts/single_use/utils/lib/bl9000_backup.sh
Executable file
165
linux_configuration/scripts/single_use/utils/lib/bl9000_backup.sh
Executable file
@ -0,0 +1,165 @@
|
||||
#!/bin/bash
|
||||
# Device data backup before rooting.
|
||||
#
|
||||
# Sourced by root_bl9000.sh; split out to keep bl9000_deps.sh under the
|
||||
# 250-line cap. Sourced rather than run, so it inherits the caller's
|
||||
# strict mode and the variables defined above the source line.
|
||||
|
||||
backup_device_data() {
|
||||
print_header "Backing Up Device Data"
|
||||
|
||||
local backup_dir
|
||||
backup_dir="${WORK_DIR}/backup_$(date +%Y%m%d_%H%M%S)"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
log "Backup directory: $backup_dir" # Check device connection first
|
||||
if ! adb get-state > /dev/null 2>&1; then
|
||||
error "Device not connected. Please connect your device first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Starting comprehensive backup process..."
|
||||
|
||||
# 1. Backup internal storage (DCIM, Pictures, Documents, Downloads, etc.)
|
||||
log "Backing up internal storage (this may take a while)..."
|
||||
local storage_dirs=("DCIM" "Pictures" "Documents" "Download" "Music" "Movies" "WhatsApp" "Telegram")
|
||||
|
||||
for dir in "${storage_dirs[@]}"; do
|
||||
if adb shell "[ -d /sdcard/$dir ]" 2> /dev/null; then
|
||||
log " → Backing up /sdcard/$dir..."
|
||||
if adb pull "/sdcard/$dir" "$backup_dir/$dir" 2>&1 | grep -v "^$"; then
|
||||
log " ✓ $dir backed up successfully"
|
||||
else
|
||||
warn " ⚠ Could not backup $dir (may be empty or inaccessible)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. Backup SMS/MMS (if possible)
|
||||
log "Backing up SMS/MMS database..."
|
||||
if adb shell "su -c 'cp /data/data/com.android.providers.telephony/databases/mmssms.db /sdcard/mmssms.db'" 2> /dev/null; then
|
||||
adb pull /sdcard/mmssms.db "$backup_dir/mmssms.db" 2> /dev/null && log " ✓ SMS/MMS backed up"
|
||||
adb shell "rm /sdcard/mmssms.db" 2> /dev/null || true
|
||||
else
|
||||
warn " ⚠ SMS/MMS backup requires root (skipping)"
|
||||
fi
|
||||
|
||||
# 3. Backup contacts
|
||||
log "Backing up contacts..."
|
||||
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/contacts2.db /sdcard/contacts2.db'" 2> /dev/null; then
|
||||
adb pull /sdcard/contacts2.db "$backup_dir/contacts2.db" 2> /dev/null && log " ✓ Contacts backed up"
|
||||
adb shell "rm /sdcard/contacts2.db" 2> /dev/null || true
|
||||
else
|
||||
warn " ⚠ Contacts backup requires root (skipping)"
|
||||
fi
|
||||
|
||||
# 4. Backup call logs
|
||||
log "Backing up call logs..."
|
||||
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/calllog.db /sdcard/calllog.db'" 2> /dev/null; then
|
||||
adb pull /sdcard/calllog.db "$backup_dir/calllog.db" 2> /dev/null && log " ✓ Call logs backed up"
|
||||
adb shell "rm /sdcard/calllog.db" 2> /dev/null || true
|
||||
else
|
||||
warn " ⚠ Call logs backup requires root (skipping)"
|
||||
fi
|
||||
|
||||
# 5. Backup app list
|
||||
log "Backing up installed apps list..."
|
||||
adb shell "pm list packages -f" > "$backup_dir/installed_apps.txt"
|
||||
log " ✓ App list saved to installed_apps.txt"
|
||||
|
||||
# 6. Backup APKs for user-installed apps (optional, can be large)
|
||||
if confirm "Backup APK files for installed apps? (This can take a long time and use lots of space)"; then
|
||||
log "Backing up user-installed APKs..."
|
||||
local apk_dir="$backup_dir/apks"
|
||||
mkdir -p "$apk_dir"
|
||||
|
||||
# Get user-installed packages
|
||||
local user_apps
|
||||
user_apps=$(adb shell "pm list packages -3 -f" | sed 's/package://' | cut -d'=' -f2)
|
||||
|
||||
local count=0
|
||||
while IFS= read -r pkg; do
|
||||
if [[ -n $pkg ]]; then
|
||||
log " → Backing up $pkg..."
|
||||
local apk_path
|
||||
apk_path=$(adb shell "pm path $pkg" | head -n1 | sed 's/package://')
|
||||
if [[ -n $apk_path ]]; then
|
||||
adb pull "$apk_path" "$apk_dir/${pkg}.apk" > /dev/null 2>&1 && count=$((count + 1))
|
||||
fi
|
||||
fi
|
||||
done <<< "$user_apps"
|
||||
|
||||
log " ✓ Backed up $count APK files"
|
||||
fi
|
||||
|
||||
# 7. Full ADB backup (app data, if device supports it)
|
||||
log "Creating full ADB backup (app data)..."
|
||||
if confirm "Create full ADB backup? (You'll need to confirm on your device)"; then
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " On your phone: Tap 'Back up my data' when prompted"
|
||||
echo " You can set a password or leave it blank"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
|
||||
if adb backup -apk -shared -all -system -f "$backup_dir/full_backup.ab"; then
|
||||
log " ✓ Full ADB backup completed"
|
||||
log " Note: Restore with: adb restore full_backup.ab"
|
||||
else
|
||||
warn " ⚠ ADB backup failed or was cancelled"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8. Backup device info
|
||||
log "Saving device information..."
|
||||
{
|
||||
echo "Device Backup Information"
|
||||
echo "========================="
|
||||
echo "Date: $(date)"
|
||||
echo
|
||||
echo "Device Model: $(adb shell getprop ro.product.model | tr -d '\r\n')"
|
||||
echo "Android Version: $(adb shell getprop ro.build.version.release | tr -d '\r\n')"
|
||||
echo "Build Number: $(adb shell getprop ro.build.display.id | tr -d '\r\n')"
|
||||
echo "Security Patch: $(adb shell getprop ro.build.version.security_patch | tr -d '\r\n')"
|
||||
echo "Serial: $(adb shell getprop ro.serialno | tr -d '\r\n')"
|
||||
echo
|
||||
echo "Installed Apps:"
|
||||
adb shell "pm list packages -3" | sed 's/package:/ - /'
|
||||
} > "$backup_dir/device_info.txt"
|
||||
|
||||
log " ✓ Device info saved"
|
||||
|
||||
# Summary
|
||||
local backup_size
|
||||
backup_size=$(du -sh "$backup_dir" 2> /dev/null | cut -f1 || echo "unknown")
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ Backup Completed Successfully! ║${NC}"
|
||||
echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}"
|
||||
echo
|
||||
log "Backup location: $backup_dir"
|
||||
log "Backup size: $backup_size"
|
||||
echo
|
||||
echo "What was backed up:"
|
||||
echo " ✓ Photos (DCIM)"
|
||||
echo " ✓ Pictures"
|
||||
echo " ✓ Documents"
|
||||
echo " ✓ Downloads"
|
||||
echo " ✓ Music"
|
||||
echo " ✓ Movies"
|
||||
echo " ✓ WhatsApp data (if present)"
|
||||
echo " ✓ Telegram data (if present)"
|
||||
echo " ✓ Installed apps list"
|
||||
echo " ✓ Device information"
|
||||
if [[ -f "$backup_dir/full_backup.ab" ]]; then
|
||||
echo " ✓ Full app data backup"
|
||||
fi
|
||||
if [[ -d "$backup_dir/apks" ]]; then
|
||||
echo " ✓ APK files"
|
||||
fi
|
||||
echo
|
||||
log "Keep this backup safe! You'll need it to restore your data after rooting."
|
||||
|
||||
return 0
|
||||
}
|
||||
157
linux_configuration/scripts/single_use/utils/lib/bl9000_deps.sh
Executable file
157
linux_configuration/scripts/single_use/utils/lib/bl9000_deps.sh
Executable file
@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
# Dependency installation and prerequisite checks.
|
||||
#
|
||||
# Sourced by root_bl9000.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
install_dependencies() {
|
||||
print_header "Installing Dependencies"
|
||||
|
||||
local packages=()
|
||||
local missing=()
|
||||
|
||||
# Check for required commands
|
||||
if ! command -v adb > /dev/null 2>&1; then
|
||||
packages+=("android-tools")
|
||||
missing+=("adb")
|
||||
fi
|
||||
|
||||
if ! command -v fastboot > /dev/null 2>&1 && ! pacman -Q android-tools > /dev/null 2>&1; then
|
||||
packages+=("android-tools")
|
||||
missing+=("fastboot")
|
||||
fi
|
||||
|
||||
if ! command -v unzip > /dev/null 2>&1; then
|
||||
packages+=("unzip")
|
||||
missing+=("unzip")
|
||||
fi
|
||||
|
||||
if ! command -v curl > /dev/null 2>&1; then
|
||||
packages+=("curl")
|
||||
missing+=("curl")
|
||||
fi
|
||||
|
||||
if ! command -v python3 > /dev/null 2>&1; then
|
||||
packages+=("python")
|
||||
missing+=("python3")
|
||||
fi
|
||||
|
||||
if ! command -v git > /dev/null 2>&1; then
|
||||
packages+=("git")
|
||||
missing+=("git")
|
||||
fi
|
||||
|
||||
# Check for libusb and fuse2 (needed for mtkclient)
|
||||
if ! pacman -Q libusb > /dev/null 2>&1; then
|
||||
packages+=("libusb")
|
||||
missing+=("libusb")
|
||||
fi
|
||||
|
||||
if ! pacman -Q fuse2 > /dev/null 2>&1; then
|
||||
packages+=("fuse2")
|
||||
missing+=("fuse2")
|
||||
fi
|
||||
|
||||
# Check for python-protobuf (needed for boot image tools)
|
||||
if ! python3 -c "import google.protobuf" 2> /dev/null; then
|
||||
packages+=("python-protobuf")
|
||||
missing+=("python-protobuf")
|
||||
fi
|
||||
|
||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||
log "All dependencies are already installed."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Missing dependencies: ${missing[*]}"
|
||||
|
||||
# Remove duplicates
|
||||
readarray -t packages < <(printf '%s\n' "${packages[@]}" | sort -u)
|
||||
|
||||
if ! confirm "Install missing packages: ${packages[*]}?"; then
|
||||
die "Cannot proceed without required dependencies."
|
||||
fi
|
||||
|
||||
log "Installing packages: ${packages[*]}"
|
||||
sudo pacman -S --needed --noconfirm "${packages[@]}" || die "Failed to install dependencies"
|
||||
|
||||
log "Dependencies installed successfully."
|
||||
}
|
||||
|
||||
setup_udev_rules() {
|
||||
print_header "Setting Up USB Access"
|
||||
|
||||
local udev_file="/etc/udev/rules.d/51-android.rules"
|
||||
local mtk_udev_dir="${WORK_DIR}/mtkclient/mtkclient/Setup/Linux"
|
||||
|
||||
# Install MTKClient udev rules if mtkclient is present
|
||||
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
|
||||
log "Installing MTKClient udev rules..."
|
||||
if [[ -d $mtk_udev_dir ]]; then
|
||||
sudo cp "$mtk_udev_dir"/*.rules /etc/udev/rules.d/ 2> /dev/null || warn "Failed to copy MTKClient rules"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f $udev_file ]]; then
|
||||
log "Android udev rules already exist at $udev_file"
|
||||
else
|
||||
if ! confirm "Create udev rules for Android device access?"; then
|
||||
warn "Skipping udev rules. You may need to run commands with sudo."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Creating Android udev rules..."
|
||||
|
||||
# Create comprehensive udev rules for Android devices
|
||||
sudo tee "$udev_file" > /dev/null << 'EOF'
|
||||
# Android Debug Bridge (ADB) devices
|
||||
# Add your device's vendor ID if not listed
|
||||
|
||||
# Google
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="adbusers"
|
||||
# MediaTek (common in BL9000)
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", MODE="0666", GROUP="adbusers"
|
||||
# Generic catch-all for Android devices
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="*", ATTR{idProduct}=="*", MODE="0666", GROUP="adbusers", SYMLINK+="android%n"
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Create adbusers group if it doesn't exist
|
||||
if ! getent group adbusers > /dev/null; then
|
||||
sudo groupadd -r adbusers
|
||||
log "Created adbusers group"
|
||||
fi
|
||||
|
||||
# Add current user to adbusers and plugdev groups
|
||||
if ! groups "$USER" | grep -q '\badbusers\b'; then
|
||||
sudo usermod -aG adbusers "$USER"
|
||||
log "Added $USER to adbusers group"
|
||||
fi
|
||||
|
||||
if ! getent group plugdev > /dev/null; then
|
||||
sudo groupadd -r plugdev
|
||||
fi
|
||||
|
||||
if ! groups "$USER" | grep -q '\bplugdev\b'; then
|
||||
sudo usermod -aG plugdev "$USER"
|
||||
log "Added $USER to plugdev group"
|
||||
fi
|
||||
|
||||
if ! getent group dialout > /dev/null; then
|
||||
sudo groupadd -r dialout
|
||||
fi
|
||||
|
||||
if ! groups "$USER" | grep -q '\bdialout\b'; then
|
||||
sudo usermod -aG dialout "$USER"
|
||||
log "Added $USER to dialout group"
|
||||
warn "You need to log out and back in for group membership to take effect."
|
||||
warn "Alternatively, run: newgrp dialout"
|
||||
fi
|
||||
|
||||
# Reload udev rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
|
||||
log "USB access configured successfully."
|
||||
}
|
||||
194
linux_configuration/scripts/single_use/utils/lib/bl9000_device.sh
Executable file
194
linux_configuration/scripts/single_use/utils/lib/bl9000_device.sh
Executable file
@ -0,0 +1,194 @@
|
||||
#!/bin/bash
|
||||
# Device detection and connection checks.
|
||||
#
|
||||
# Sourced by root_bl9000.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
check_device() {
|
||||
print_header "Checking Device Connection"
|
||||
|
||||
log "Starting ADB server..."
|
||||
adb start-server > /dev/null 2>&1 || true
|
||||
|
||||
log "Waiting for device..."
|
||||
if ! adb wait-for-device; then
|
||||
error "Failed to detect device via ADB."
|
||||
echo
|
||||
echo "Troubleshooting steps:"
|
||||
echo "1. Make sure USB debugging is enabled on your phone"
|
||||
echo " Settings → About Phone → Tap Build Number 7 times"
|
||||
echo " Settings → Developer Options → Enable USB Debugging"
|
||||
echo "2. Connect your phone via USB cable"
|
||||
echo "3. Accept the 'Allow USB debugging' prompt on your phone"
|
||||
echo "4. Run: adb devices"
|
||||
echo
|
||||
return 1
|
||||
fi
|
||||
|
||||
local device_info
|
||||
device_info=$(adb devices -l | grep -v "List of devices" | grep -v "^$" | head -n1)
|
||||
|
||||
if [[ -z $device_info ]]; then
|
||||
error "No device detected"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Device connected: $device_info"
|
||||
|
||||
# Check device properties
|
||||
local model
|
||||
model=$(adb shell getprop ro.product.model 2> /dev/null | tr -d '\r\n' || echo "Unknown")
|
||||
log "Model: $model"
|
||||
|
||||
local android_version
|
||||
android_version=$(adb shell getprop ro.build.version.release 2> /dev/null | tr -d '\r\n' || echo "Unknown")
|
||||
log "Android version: $android_version"
|
||||
|
||||
local battery_level
|
||||
battery_level=$(adb shell dumpsys battery | grep level | awk '{print $2}' | tr -d '\r\n' || echo "Unknown")
|
||||
log "Battery level: ${battery_level}%"
|
||||
|
||||
if [[ $battery_level != "Unknown" && $battery_level -lt 50 ]]; then
|
||||
warn "Battery level is below 50%. Charge your phone before proceeding."
|
||||
if ! confirm "Continue anyway?"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if bootloader is unlocked
|
||||
local unlock_status
|
||||
unlock_status=$(adb shell getprop ro.boot.verifiedbootstate 2> /dev/null | tr -d '\r\n' || echo "unknown")
|
||||
if [[ $unlock_status == "orange" || $unlock_status == "red" ]]; then
|
||||
log "Bootloader unlock status: ${GREEN}UNLOCKED${NC}"
|
||||
else
|
||||
warn "Bootloader appears to be LOCKED. You'll need to unlock it to root."
|
||||
fi
|
||||
|
||||
# Check if OEM unlocking is enabled
|
||||
local oem_unlock
|
||||
oem_unlock=$(adb shell getprop sys.oem_unlock_allowed 2> /dev/null | tr -d '\r\n' || echo "unknown")
|
||||
if [[ $oem_unlock == "1" ]]; then
|
||||
log "OEM unlocking: ${GREEN}ENABLED${NC}"
|
||||
else
|
||||
warn "OEM unlocking is not enabled in Developer Options."
|
||||
echo "Enable it at: Settings → Developer Options → OEM unlocking"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
unlock_bootloader() {
|
||||
print_header "Unlocking Bootloader"
|
||||
|
||||
echo
|
||||
echo -e "${RED}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${RED}║ WARNING ║${NC}"
|
||||
echo -e "${RED}║ ║${NC}"
|
||||
echo -e "${RED}║ Unlocking the bootloader will ERASE ALL DATA on your phone! ║${NC}"
|
||||
echo -e "${RED}║ ║${NC}"
|
||||
echo -e "${RED}║ This includes: ║${NC}"
|
||||
echo -e "${RED}║ - All apps and app data ║${NC}"
|
||||
echo -e "${RED}║ - Photos, videos, and files ║${NC}"
|
||||
echo -e "${RED}║ - System settings ║${NC}"
|
||||
echo -e "${RED}║ - Everything else on internal storage ║${NC}"
|
||||
echo -e "${RED}║ ║${NC}"
|
||||
echo -e "${RED}║ Make sure you have backed up important data! ║${NC}"
|
||||
echo -e "${RED}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo
|
||||
|
||||
if ! confirm "Have you backed up all important data and want to proceed?"; then
|
||||
log "Bootloader unlock cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! confirm "Are you ABSOLUTELY SURE? This cannot be undone!"; then
|
||||
log "Bootloader unlock cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Rebooting device to bootloader..."
|
||||
adb reboot bootloader || die "Failed to reboot to bootloader"
|
||||
|
||||
log "Waiting for fastboot mode..."
|
||||
sleep 5
|
||||
|
||||
if ! fastboot devices | grep -q .; then
|
||||
error "Device not detected in fastboot mode."
|
||||
echo
|
||||
echo "If the device doesn't enter fastboot automatically:"
|
||||
echo "1. Power off the phone completely"
|
||||
echo "2. Hold Volume Down + Power buttons simultaneously"
|
||||
echo "3. Release when you see the bootloader/fastboot screen"
|
||||
echo "4. Run: fastboot devices"
|
||||
echo
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Device in fastboot mode"
|
||||
|
||||
# Check current bootloader status
|
||||
local bl_status
|
||||
bl_status=$(fastboot getvar unlocked 2>&1 | grep "unlocked:" | awk '{print $2}' || echo "unknown")
|
||||
if [[ $bl_status == "yes" ]]; then
|
||||
log "Bootloader is already unlocked."
|
||||
fastboot reboot
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Attempting to unlock bootloader..."
|
||||
|
||||
# Try different unlock commands (varies by device)
|
||||
if fastboot flashing unlock 2>&1 | grep -qi "okay\|finished"; then
|
||||
log "Bootloader unlock command sent successfully."
|
||||
elif fastboot oem unlock 2>&1 | grep -qi "okay\|finished"; then
|
||||
log "Bootloader unlock command sent successfully."
|
||||
else
|
||||
error "Bootloader unlock command may have failed."
|
||||
echo
|
||||
echo "On your phone:"
|
||||
echo "1. Use volume buttons to select 'Unlock the bootloader'"
|
||||
echo "2. Press power button to confirm"
|
||||
echo
|
||||
|
||||
if ! confirm "Did you complete the unlock on the device?"; then
|
||||
fastboot reboot
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Rebooting device..."
|
||||
fastboot reboot || true
|
||||
|
||||
log "Bootloader unlocked successfully!"
|
||||
log "Device will now boot up and perform factory reset..."
|
||||
log "Waiting for device to come back online..."
|
||||
|
||||
sleep 10
|
||||
adb wait-for-device || true
|
||||
|
||||
log "Please complete the initial setup on your phone, then re-enable USB debugging."
|
||||
echo
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
download_magisk() {
|
||||
print_header "Downloading Magisk"
|
||||
|
||||
local magisk_apk="$WORK_DIR/magisk.apk"
|
||||
|
||||
if [[ -f $magisk_apk ]]; then
|
||||
log "Magisk APK already downloaded at $magisk_apk"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Downloading latest Magisk APK..."
|
||||
if ! curl -L -o "$magisk_apk" "$MAGISK_APK_URL"; then
|
||||
error "Failed to download Magisk APK"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Magisk downloaded successfully: $magisk_apk"
|
||||
return 0
|
||||
}
|
||||
144
linux_configuration/scripts/single_use/utils/lib/bl9000_magisk.sh
Executable file
144
linux_configuration/scripts/single_use/utils/lib/bl9000_magisk.sh
Executable file
@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
# Boot image patching with Magisk.
|
||||
#
|
||||
# Sourced by root_bl9000.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
patch_boot_with_magisk() {
|
||||
print_header "Patching Boot Image with Magisk"
|
||||
|
||||
if [[ ! -f ${BOOT_IMG:-} ]]; then
|
||||
die "Boot image not found: ${BOOT_IMG:-none}"
|
||||
fi
|
||||
|
||||
local magisk_apk="$WORK_DIR/magisk.apk"
|
||||
if [[ ! -f $magisk_apk ]]; then
|
||||
die "Magisk APK not found. Run download step first."
|
||||
fi
|
||||
|
||||
log "Checking if device is connected..."
|
||||
if ! adb devices | grep -q "device$"; then
|
||||
die "No device detected. Make sure USB debugging is enabled and device is connected."
|
||||
fi
|
||||
|
||||
log "Installing Magisk APK on device..."
|
||||
if ! adb install -r "$magisk_apk" 2> /dev/null; then
|
||||
warn "Magisk APK installation failed (may already be installed)"
|
||||
fi
|
||||
|
||||
log "Pushing boot image to device..."
|
||||
adb push "$BOOT_IMG" /sdcard/Download/boot.img || die "Failed to push boot image"
|
||||
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " MANUAL STEP REQUIRED"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
echo "On your phone:"
|
||||
echo "1. Open the Magisk app"
|
||||
echo "2. Tap 'Install' next to Magisk"
|
||||
echo "3. Select 'Select and Patch a File'"
|
||||
echo "4. Navigate to Downloads and select boot.img"
|
||||
echo "5. Tap 'Let's Go' and wait for patching to complete"
|
||||
echo "6. The patched file will be saved as magisk_patched_*.img"
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
|
||||
if ! confirm "Have you completed patching the boot image in Magisk app?"; then
|
||||
error "Patching cancelled by user"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Pulling patched boot image from device..."
|
||||
|
||||
local patched_img
|
||||
patched_img=$(adb shell "ls /sdcard/Download/magisk_patched_*.img 2>/dev/null" | tr -d '\r\n' | head -n1 || echo "")
|
||||
|
||||
if [[ -z $patched_img ]]; then
|
||||
error "Could not find patched boot image on device."
|
||||
echo "Please ensure the patching completed successfully in Magisk app."
|
||||
return 1
|
||||
fi
|
||||
|
||||
PATCHED_BOOT_IMG="$WORK_DIR/magisk_patched.img"
|
||||
if ! adb pull "$patched_img" "$PATCHED_BOOT_IMG"; then
|
||||
error "Failed to pull patched boot image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Patched boot image saved to: $PATCHED_BOOT_IMG"
|
||||
return 0
|
||||
}
|
||||
|
||||
flash_patched_boot() {
|
||||
print_header "Flashing Patched Boot Image"
|
||||
|
||||
if [[ ! -f ${PATCHED_BOOT_IMG:-} ]]; then
|
||||
die "Patched boot image not found: ${PATCHED_BOOT_IMG:-none}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${YELLOW}This will flash the patched boot image to your device.${NC}"
|
||||
echo "Device uses A/B partitions - will flash to boot_a"
|
||||
echo
|
||||
|
||||
if ! confirm "Proceed with flashing?"; then
|
||||
log "Flashing cancelled by user"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Rebooting to bootloader..."
|
||||
adb reboot bootloader || die "Failed to reboot to bootloader"
|
||||
|
||||
log "Waiting for fastboot mode..."
|
||||
sleep 5
|
||||
|
||||
if ! sudo fastboot devices | grep -q .; then
|
||||
die "Device not detected in fastboot mode"
|
||||
fi
|
||||
|
||||
log "Flashing patched boot image to boot_a..."
|
||||
if ! sudo fastboot flash boot_a "$PATCHED_BOOT_IMG"; then
|
||||
error "Failed to flash boot image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Flashed successfully!"
|
||||
log "Rebooting device..."
|
||||
sudo fastboot reboot
|
||||
|
||||
log "Waiting for device to boot..."
|
||||
sleep 10
|
||||
adb wait-for-device || true
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ Root Process Complete! ║${NC}"
|
||||
echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}"
|
||||
echo
|
||||
echo "Your BL9000 phone should now be rooted with Magisk!"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo "1. Open the Magisk app on your phone"
|
||||
echo "2. Verify that it shows 'Installed' for both Magisk and App"
|
||||
echo "3. Grant root access to apps as needed"
|
||||
echo "4. Install Magisk modules if desired"
|
||||
echo
|
||||
echo "Note: Some banking and secure apps may not work with root."
|
||||
echo " Use Magisk's DenyList feature to hide root from specific apps."
|
||||
echo
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
clean_work_dir() {
|
||||
if [[ -d $WORK_DIR ]]; then
|
||||
log "Removing working directory: $WORK_DIR"
|
||||
rm -rf "$WORK_DIR"
|
||||
log "Cleaned successfully"
|
||||
else
|
||||
log "Work directory doesn't exist: $WORK_DIR"
|
||||
fi
|
||||
}
|
||||
187
linux_configuration/scripts/single_use/utils/lib/bl9000_mtkclient.sh
Executable file
187
linux_configuration/scripts/single_use/utils/lib/bl9000_mtkclient.sh
Executable file
@ -0,0 +1,187 @@
|
||||
#!/bin/bash
|
||||
# mtkclient checkout and setup.
|
||||
#
|
||||
# Sourced by root_bl9000.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
install_mtkclient() {
|
||||
print_header "Installing MTKClient"
|
||||
|
||||
local mtk_dir="${WORK_DIR}/mtkclient"
|
||||
|
||||
if [[ -d $mtk_dir && -f "$mtk_dir/mtk.py" ]]; then
|
||||
log "MTKClient already installed at $mtk_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! confirm "Install MTKClient for MediaTek boot image extraction?"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Cloning MTKClient repository..."
|
||||
if ! git clone https://github.com/bkerler/mtkclient "$mtk_dir"; then
|
||||
error "Failed to clone MTKClient"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Installing MTKClient Python dependencies..."
|
||||
# Subshell so the cd cannot leak, and so a failure cannot strand the caller
|
||||
# in $mtk_dir the way the old `cd -` did on an early return.
|
||||
(
|
||||
cd "$mtk_dir" || exit 1
|
||||
python3 -m pip install --user -r requirements.txt || warn "Some dependencies may have failed to install"
|
||||
python3 -m pip install --user . || warn "MTKClient installation may be incomplete"
|
||||
) || warn "MTKClient installation may be incomplete"
|
||||
|
||||
log "MTKClient installed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
extract_boot_with_mtkclient() {
|
||||
print_header "Extracting Boot with MTKClient"
|
||||
|
||||
local mtk_dir="${WORK_DIR}/mtkclient"
|
||||
local boot_img="$WORK_DIR/boot.img"
|
||||
local boot_a_img="$WORK_DIR/boot_a.img"
|
||||
local vbmeta_a_img="$WORK_DIR/vbmeta_a.img"
|
||||
|
||||
if [[ ! -d $mtk_dir ]]; then
|
||||
error "MTKClient not installed. Run: $SCRIPT_NAME install-mtk"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " MTKClient Boot ROM Mode Instructions"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
echo "BEFORE continuing, you MUST:"
|
||||
echo
|
||||
echo " 1. Power off your phone COMPLETELY"
|
||||
echo " 2. DISCONNECT the USB cable from your phone"
|
||||
echo " 3. Have the USB cable ready in your hand"
|
||||
echo
|
||||
echo "When you press Enter:"
|
||||
echo
|
||||
echo " 4. Press and hold BOTH Volume buttons (Up + Down)"
|
||||
echo " 5. While holding BOTH buttons, connect USB cable"
|
||||
echo " 6. Keep holding until device is detected (may take 5-10 seconds)"
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
|
||||
if ! confirm "Phone is OFF and USB DISCONNECTED - ready to proceed?"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Starting MTKClient NOW - quickly enter BROM mode!"
|
||||
log "Hold BOTH volume buttons and connect USB cable..."
|
||||
echo
|
||||
|
||||
# Checked BEFORE the cd, so the early return needs no `cd -`. A subshell is
|
||||
# not an option here: this function exports BOOT_IMG to its caller.
|
||||
if [[ ! -d "$mtk_dir/venv" ]]; then
|
||||
error "MTKClient virtual environment not found. Run: $SCRIPT_NAME install-mtk"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local prev_dir="$PWD"
|
||||
cd "$mtk_dir" || {
|
||||
error "Cannot enter $mtk_dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source venv/bin/activate
|
||||
# BL9000 uses A/B partitions, so extract boot_a and vbmeta_a
|
||||
if python3 mtk.py r boot_a,vbmeta_a "$boot_a_img,$vbmeta_a_img"; then
|
||||
log "Boot_a and vbmeta_a extracted successfully!"
|
||||
|
||||
# Copy boot_a.img to boot.img for Magisk compatibility
|
||||
cp "$boot_a_img" "$boot_img"
|
||||
BOOT_IMG="$boot_img"
|
||||
|
||||
log "Boot image ready for patching: $BOOT_IMG"
|
||||
|
||||
# Reset device
|
||||
log "Resetting device..."
|
||||
python3 mtk.py reset || warn "Failed to reset device, please reboot manually"
|
||||
|
||||
# Deactivate venv if function exists
|
||||
type deactivate &> /dev/null && deactivate
|
||||
|
||||
cd "$prev_dir" || true
|
||||
return 0
|
||||
else
|
||||
# Deactivate venv if function exists
|
||||
type deactivate &> /dev/null && deactivate
|
||||
|
||||
error "Failed to extract boot image with MTKClient"
|
||||
cd "$prev_dir" || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
extract_boot_image() {
|
||||
print_header "Extracting Boot Image"
|
||||
|
||||
local boot_img="$WORK_DIR/boot.img"
|
||||
|
||||
if [[ -n ${BOOT_IMG:-} && -f $BOOT_IMG ]]; then
|
||||
log "Using provided boot image: $BOOT_IMG"
|
||||
cp "$BOOT_IMG" "$boot_img"
|
||||
BOOT_IMG="$boot_img"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Attempting to extract boot image from device..."
|
||||
|
||||
# Method 1: Try MTKClient first (best for MediaTek devices)
|
||||
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
|
||||
log "Trying MTKClient extraction..."
|
||||
if extract_boot_with_mtkclient; then
|
||||
return 0
|
||||
fi
|
||||
warn "MTKClient extraction failed, trying ADB methods..."
|
||||
fi
|
||||
|
||||
# Method 2: Try to pull boot partition directly via ADB
|
||||
local boot_partition
|
||||
boot_partition=$(adb shell "find /dev/block -name boot | head -n1" 2> /dev/null | tr -d '\r\n' || echo "")
|
||||
|
||||
if [[ -n $boot_partition ]]; then
|
||||
log "Found boot partition: $boot_partition"
|
||||
if adb pull "$boot_partition" "$boot_img" 2> /dev/null; then
|
||||
log "Boot image extracted successfully"
|
||||
BOOT_IMG="$boot_img"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 3: Try to get boot partition via by-name
|
||||
boot_partition=$(adb shell "ls /dev/block/by-name/boot*" 2> /dev/null | head -n1 | tr -d '\r\n' || echo "")
|
||||
|
||||
if [[ -n $boot_partition ]]; then
|
||||
log "Found boot partition: $boot_partition"
|
||||
if adb shell "su -c 'dd if=$boot_partition of=/sdcard/boot.img'" 2> /dev/null &&
|
||||
adb pull /sdcard/boot.img "$boot_img" 2> /dev/null; then
|
||||
adb shell rm /sdcard/boot.img 2> /dev/null || true
|
||||
log "Boot image extracted successfully"
|
||||
BOOT_IMG="$boot_img"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
error "Failed to extract boot image automatically."
|
||||
echo
|
||||
echo "Manual extraction options:"
|
||||
echo "1. Use MTKClient: $SCRIPT_NAME extract-mtk"
|
||||
echo "2. Extract boot.img from your device's firmware package"
|
||||
echo "3. Get boot.img from device manufacturer's official ROM"
|
||||
echo
|
||||
echo "Then run: $SCRIPT_NAME root --boot-img /path/to/boot.img"
|
||||
echo
|
||||
|
||||
return 1
|
||||
}
|
||||
107
linux_configuration/scripts/single_use/utils/lib/bl9000_usage.sh
Executable file
107
linux_configuration/scripts/single_use/utils/lib/bl9000_usage.sh
Executable file
@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
# Usage text and the argument parsing help.
|
||||
#
|
||||
# Sourced by root_bl9000.sh; split out to keep it under the 250-line cap.
|
||||
# Sourced rather than run, so it inherits the caller's strict mode and
|
||||
# the helper functions and variables defined above the source line.
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $SCRIPT_NAME [OPTIONS] [COMMAND]
|
||||
|
||||
Root BL9000 phone from Arch Linux using Magisk.
|
||||
|
||||
Commands:
|
||||
install-deps Install required dependencies (adb, fastboot, tools)
|
||||
install-mtk Install MTKClient for MediaTek boot extraction
|
||||
check Check device connection and prerequisites
|
||||
backup Backup phone data before unlocking bootloader
|
||||
unlock Unlock bootloader (WARNING: wipes all data!)
|
||||
extract-mtk Extract boot.img using MTKClient (for MediaTek devices)
|
||||
patch Patch boot.img with Magisk (manual step on phone)
|
||||
flash [IMG] Flash patched boot image (uses magisk_patched.img or specified file)
|
||||
auto-root Automated: extract -> patch -> flash in one command
|
||||
root Extract boot, patch with Magisk, and flash
|
||||
full Run complete rooting process (deps + unlock + root)
|
||||
clean Remove temporary working directory
|
||||
help Show this message
|
||||
|
||||
Options:
|
||||
-h, --help Show this message
|
||||
--work-dir DIR Set working directory (default: $WORK_DIR)
|
||||
--boot-img FILE Use existing boot.img instead of extracting from device
|
||||
|
||||
Examples:
|
||||
$SCRIPT_NAME install-deps # Install required tools
|
||||
$SCRIPT_NAME install-mtk # Install MTKClient for boot extraction
|
||||
$SCRIPT_NAME check # Verify device connection
|
||||
$SCRIPT_NAME backup # Backup phone data first!
|
||||
$SCRIPT_NAME extract-mtk # Extract boot.img with MTKClient
|
||||
$SCRIPT_NAME auto-root # Automated rooting (extract + patch + flash)
|
||||
$SCRIPT_NAME full # Complete rooting process
|
||||
$SCRIPT_NAME root # Root only (assumes bootloader unlocked)
|
||||
|
||||
WARNING: Unlocking the bootloader will ERASE ALL DATA on your phone!
|
||||
Make sure to back up important data before proceeding.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
|
||||
run_full_process() {
|
||||
print_header "BL9000 Full Root Process"
|
||||
|
||||
log "Starting complete rooting process..."
|
||||
|
||||
install_dependencies || die "Failed to install dependencies"
|
||||
setup_udev_rules || true
|
||||
|
||||
echo
|
||||
if ! confirm "Continue to device check?"; then
|
||||
die "Process cancelled by user"
|
||||
fi
|
||||
|
||||
check_device || die "Device check failed"
|
||||
|
||||
echo
|
||||
if ! confirm "Continue to backup device data?"; then
|
||||
die "Process cancelled by user"
|
||||
fi
|
||||
|
||||
backup_device_data || warn "Backup failed or incomplete"
|
||||
|
||||
echo
|
||||
if ! confirm "Continue to bootloader unlock?"; then
|
||||
die "Process cancelled by user"
|
||||
fi
|
||||
|
||||
unlock_bootloader || die "Bootloader unlock failed"
|
||||
|
||||
echo
|
||||
log "Please complete device setup and re-enable USB debugging, then press Enter..."
|
||||
read -r
|
||||
|
||||
check_device || die "Device check failed after unlock"
|
||||
|
||||
download_magisk || die "Failed to download Magisk"
|
||||
extract_boot_image || die "Failed to extract boot image"
|
||||
patch_boot_with_magisk || die "Failed to patch boot image"
|
||||
flash_patched_boot || die "Failed to flash patched boot"
|
||||
|
||||
log "Full root process completed successfully!"
|
||||
}
|
||||
|
||||
run_root_only() {
|
||||
print_header "BL9000 Root Process (Skip Unlock)"
|
||||
|
||||
log "Starting root process (assuming bootloader is already unlocked)..."
|
||||
|
||||
check_device || die "Device check failed"
|
||||
download_magisk || die "Failed to download Magisk"
|
||||
extract_boot_image || die "Failed to extract boot image"
|
||||
patch_boot_with_magisk || die "Failed to patch boot image"
|
||||
flash_patched_boot || die "Failed to flash patched boot"
|
||||
|
||||
log "Root process completed successfully!"
|
||||
}
|
||||
161
linux_configuration/scripts/single_use/utils/lib/docs_all.sh
Executable file
161
linux_configuration/scripts/single_use/utils/lib/docs_all.sh
Executable file
@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
# Cross-language lookup and content extraction.
|
||||
#
|
||||
# Sourced by lookup_docs.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
#==============================================================================
|
||||
# Generic lookup (searches all languages)
|
||||
#==============================================================================
|
||||
lookup_all() {
|
||||
local term="$1"
|
||||
|
||||
# Try each language
|
||||
for lang in python cpp js rust go shell; do
|
||||
local result
|
||||
result=$(lookup_$lang "$term" 2>/dev/null)
|
||||
if [ -n "$result" ]; then
|
||||
echo "$lang: $result"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Parse Python import and lookup the actual imported item
|
||||
#==============================================================================
|
||||
parse_python_import() {
|
||||
local import_line="$1"
|
||||
|
||||
# Handle "from X import Y" format
|
||||
if [[ $import_line =~ ^from[[:space:]]+([^[:space:]]+)[[:space:]]+import[[:space:]]+(.+) ]]; then
|
||||
local module="${BASH_REMATCH[1]}"
|
||||
local items="${BASH_REMATCH[2]}"
|
||||
|
||||
# Clean up items (remove parentheses, commas, etc.)
|
||||
items=$(echo "$items" | sed 's/[(),]//g' | awk '{print $1}')
|
||||
|
||||
# Output: module and first imported item
|
||||
echo "$module|$items"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Handle "import X" format
|
||||
if [[ $import_line =~ ^import[[:space:]]+([^[:space:],]+) ]]; then
|
||||
local module="${BASH_REMATCH[1]}"
|
||||
echo "$module|"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Smart lookup for imports
|
||||
#==============================================================================
|
||||
lookup_import() {
|
||||
local import_line="$1"
|
||||
local lang="$2"
|
||||
|
||||
case "$lang" in
|
||||
python)
|
||||
local parsed
|
||||
parsed=$(parse_python_import "$import_line")
|
||||
if [ -n "$parsed" ]; then
|
||||
local module item
|
||||
module=$(echo "$parsed" | cut -d'|' -f1)
|
||||
item=$(echo "$parsed" | cut -d'|' -f2)
|
||||
|
||||
# For "from X import Y", look up Y within module X's documentation
|
||||
if [ -n "$item" ] && [ -n "$module" ]; then
|
||||
local result
|
||||
# Pass both item and module to lookup_python
|
||||
result=$(lookup_python "$item" "$module")
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fall back to module documentation
|
||||
lookup_python "$module"
|
||||
fi
|
||||
;;
|
||||
|
||||
c_cpp)
|
||||
# Extract header name from #include <header> or #include "header"
|
||||
local header
|
||||
header=$(echo "$import_line" | sed -E 's/#include\s*[<"]([^">]+)[">]/\1/' | sed 's/\.h$//')
|
||||
lookup_cpp "$header"
|
||||
;;
|
||||
|
||||
javascript | typescript)
|
||||
# Extract module from import/require
|
||||
local module=""
|
||||
# Match: from "module" or from 'module'
|
||||
module=$(echo "$import_line" | grep -oP "from\s+['\"]\\K[^'\"]+")
|
||||
if [ -z "$module" ]; then
|
||||
# Match: require("module") or require('module')
|
||||
module=$(echo "$import_line" | grep -oP "require\\(['\"]\\K[^'\"]+")
|
||||
fi
|
||||
[ -n "$module" ] && lookup_js "$module"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown language: $lang"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Extract documentation content
|
||||
#==============================================================================
|
||||
extract_doc_content() {
|
||||
local file="$1"
|
||||
local term="$2"
|
||||
local max_lines="${3:-20}"
|
||||
|
||||
if [[ $file == *.html ]]; then
|
||||
# Extract text from HTML, find section about term
|
||||
if command -v html2text &>/dev/null; then
|
||||
html2text "$file" 2>/dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
|
||||
elif command -v lynx &>/dev/null; then
|
||||
lynx -dump -nolist "$file" 2>/dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
|
||||
else
|
||||
# Basic extraction
|
||||
sed 's/<[^>]*>//g' "$file" | grep -A"$max_lines" -i "$term" | head -"$max_lines"
|
||||
fi
|
||||
elif [[ $file == *.json ]]; then
|
||||
# Pretty print JSON section
|
||||
grep -A5 "\"$term\"" "$file" 2>/dev/null
|
||||
else
|
||||
# Plain text
|
||||
grep -A"$max_lines" -i "$term" "$file" | head -"$max_lines"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Main
|
||||
#==============================================================================
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <term> [language] [options]
|
||||
|
||||
Search offline documentation for a term.
|
||||
|
||||
Languages: python, cpp, c_cpp, js, javascript, rust, go, shell, all
|
||||
|
||||
Options:
|
||||
--open Open the documentation file (requires xdg-open)
|
||||
--extract Extract and display relevant content
|
||||
--import Parse and lookup an import statement
|
||||
--batch Process multiple terms from a file
|
||||
|
||||
Examples:
|
||||
$0 Path python # Find Path in Python docs
|
||||
$0 vector cpp # Find vector in C++ docs
|
||||
$0 map # Find map in all languages
|
||||
$0 --import "from pathlib import Path" python
|
||||
$0 --batch imports.txt python
|
||||
EOF
|
||||
}
|
||||
93
linux_configuration/scripts/single_use/utils/lib/docs_cpp.sh
Executable file
93
linux_configuration/scripts/single_use/utils/lib/docs_cpp.sh
Executable file
@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# C++ documentation lookup.
|
||||
#
|
||||
# Sourced by lookup_docs.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
#==============================================================================
|
||||
# C/C++ specific lookup
|
||||
#==============================================================================
|
||||
lookup_cpp() {
|
||||
local term="$1"
|
||||
local doc_dir="$DOCS_DIR/c_cpp"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
# Resolve symlink if present (system package installs to c_cpp/system/)
|
||||
[ -L "$doc_dir/system" ] && doc_dir="$doc_dir/system"
|
||||
|
||||
# Common C headers
|
||||
case "$term" in
|
||||
stdio.h | stdio)
|
||||
[ -f "$doc_dir/reference/cstdio/index.html" ] && result="$doc_dir/reference/cstdio/index.html"
|
||||
[ -f "$doc_dir/en/c/io.html" ] && result="$doc_dir/en/c/io.html"
|
||||
desc="C standard I/O header"
|
||||
;;
|
||||
stdlib.h | stdlib)
|
||||
[ -f "$doc_dir/reference/cstdlib/index.html" ] && result="$doc_dir/reference/cstdlib/index.html"
|
||||
[ -f "$doc_dir/en/c/memory.html" ] && result="$doc_dir/en/c/memory.html"
|
||||
desc="C standard library header"
|
||||
;;
|
||||
string.h | cstring)
|
||||
[ -f "$doc_dir/reference/cstring/index.html" ] && result="$doc_dir/reference/cstring/index.html"
|
||||
desc="C string handling header"
|
||||
;;
|
||||
math.h | cmath)
|
||||
[ -f "$doc_dir/reference/cmath/index.html" ] && result="$doc_dir/reference/cmath/index.html"
|
||||
desc="C math header"
|
||||
;;
|
||||
esac
|
||||
|
||||
# C++ STL containers
|
||||
case "$term" in
|
||||
vector)
|
||||
[ -f "$doc_dir/reference/vector/index.html" ] && result="$doc_dir/reference/vector/index.html"
|
||||
[ -f "$doc_dir/en/cpp/container/vector.html" ] && result="$doc_dir/en/cpp/container/vector.html"
|
||||
desc="C++ std::vector container"
|
||||
;;
|
||||
map)
|
||||
[ -f "$doc_dir/reference/map/index.html" ] && result="$doc_dir/reference/map/index.html"
|
||||
desc="C++ std::map container"
|
||||
;;
|
||||
string)
|
||||
[ -f "$doc_dir/reference/string/index.html" ] && result="$doc_dir/reference/string/index.html"
|
||||
desc="C++ std::string"
|
||||
;;
|
||||
iostream)
|
||||
[ -f "$doc_dir/reference/iostream/index.html" ] && result="$doc_dir/reference/iostream/index.html"
|
||||
desc="C++ iostream header"
|
||||
;;
|
||||
esac
|
||||
|
||||
# C keywords
|
||||
case "$term" in
|
||||
if | else | for | while | do | switch | case | break | continue | return | goto)
|
||||
[ -f "$doc_dir/en/c/language/$term.html" ] && result="$doc_dir/en/c/language/$term.html"
|
||||
[ -f "$doc_dir/en/cpp/language/$term.html" ] && result="$doc_dir/en/cpp/language/$term.html"
|
||||
desc="C/C++ keyword: $term"
|
||||
;;
|
||||
int | char | float | double | void | long | short | unsigned | signed)
|
||||
[ -f "$doc_dir/en/c/language/type.html" ] && result="$doc_dir/en/c/language/type.html"
|
||||
desc="C/C++ type: $term"
|
||||
;;
|
||||
struct | union | enum | typedef)
|
||||
[ -f "$doc_dir/en/c/language/$term.html" ] && result="$doc_dir/en/c/language/$term.html"
|
||||
desc="C/C++ keyword: $term"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Search in files if not found (use -L to follow symlinks)
|
||||
if [ -z "$result" ]; then
|
||||
local found
|
||||
found=$(find -L "$doc_dir" -name "*${term}*" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$found" ]; then
|
||||
result="$found"
|
||||
desc="C/C++: $term"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
166
linux_configuration/scripts/single_use/utils/lib/docs_js.sh
Executable file
166
linux_configuration/scripts/single_use/utils/lib/docs_js.sh
Executable file
@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
# JavaScript documentation lookup.
|
||||
#
|
||||
# Sourced by lookup_docs.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
#==============================================================================
|
||||
# JavaScript/MDN specific lookup
|
||||
# Searches the cloned MDN content repository
|
||||
#==============================================================================
|
||||
lookup_js() {
|
||||
local term="$1"
|
||||
local mdn_dir="$DOCS_DIR/mdn-content/files/en-us"
|
||||
|
||||
# Normalize term for searching
|
||||
local term_lower
|
||||
term_lower=$(echo "$term" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Handle common statement aliases (MDN uses if...else, try...catch, etc.)
|
||||
local statement_aliases=(
|
||||
"if:if...else"
|
||||
"else:if...else"
|
||||
"try:try...catch"
|
||||
"catch:try...catch"
|
||||
"finally:try...catch"
|
||||
"do:do...while"
|
||||
"while:while"
|
||||
"for:for"
|
||||
"switch:switch"
|
||||
"case:switch"
|
||||
"default:switch"
|
||||
)
|
||||
|
||||
for alias in "${statement_aliases[@]}"; do
|
||||
local key="${alias%%:*}"
|
||||
local value="${alias##*:}"
|
||||
if [ "$term_lower" = "$key" ]; then
|
||||
local stmt_dir="$mdn_dir/web/javascript/reference/statements/$value"
|
||||
if [ -d "$stmt_dir" ] && [ -f "$stmt_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$stmt_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$stmt_dir/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle boolean/null literals
|
||||
case "$term_lower" in
|
||||
true | false)
|
||||
local bool_dir="$mdn_dir/web/javascript/reference/global_objects/boolean"
|
||||
if [ -d "$bool_dir" ] && [ -f "$bool_dir/index.md" ]; then
|
||||
echo "$bool_dir/index.md|Boolean ($term)"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
null)
|
||||
local null_dir="$mdn_dir/web/javascript/reference/operators/null"
|
||||
if [ -d "$null_dir" ] && [ -f "$null_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$null_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$null_dir/index.md|${title:-null}"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
undefined)
|
||||
local undef_dir="$mdn_dir/web/javascript/reference/global_objects/undefined"
|
||||
if [ -d "$undef_dir" ] && [ -f "$undef_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$undef_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$undef_dir/index.md|${title:-undefined}"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Search JavaScript reference directory structure (priority order)
|
||||
local search_dirs=(
|
||||
"$mdn_dir/web/javascript/reference/statements"
|
||||
"$mdn_dir/web/javascript/reference/operators"
|
||||
"$mdn_dir/web/javascript/reference/global_objects"
|
||||
"$mdn_dir/web/javascript/reference/functions"
|
||||
"$mdn_dir/web/javascript/reference/classes"
|
||||
)
|
||||
|
||||
for search_dir in "${search_dirs[@]}"; do
|
||||
if [ -d "$search_dir" ]; then
|
||||
# Look for exact directory match (MDN uses directories with index.md)
|
||||
local found_dir
|
||||
found_dir=$(find "$search_dir" -maxdepth 2 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found_dir" ] && [ -f "$found_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found_dir/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Search Web APIs - prioritize *_api directories for common terms
|
||||
if [ -d "$mdn_dir/web/api" ]; then
|
||||
# First try <term>_api directory (e.g., fetch_api, console_api)
|
||||
local api_dir="$mdn_dir/web/api/${term_lower}_api"
|
||||
if [ -d "$api_dir" ] && [ -f "$api_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$api_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$api_dir/index.md|${title:-$term API}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Then try exact top-level API interface (e.g., Console, Document, Element)
|
||||
local found
|
||||
found=$(find "$mdn_dir/web/api" -maxdepth 1 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try window/<term> for global functions like alert, confirm, etc.
|
||||
local window_method="$mdn_dir/web/api/window/${term_lower}"
|
||||
if [ -d "$window_method" ] && [ -f "$window_method/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$window_method/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$window_method/index.md|${title:-Window.$term()}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Search nested API methods
|
||||
found=$(find "$mdn_dir/web/api" -maxdepth 3 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Now try partial matches in Global Objects (e.g., Array.from, Object.keys)
|
||||
if [ -d "$mdn_dir/web/javascript/reference/global_objects" ]; then
|
||||
local found
|
||||
found=$(find "$mdn_dir/web/javascript/reference/global_objects" -maxdepth 2 -type d -iname "*${term}*" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Glossary as last resort
|
||||
if [ -d "$mdn_dir/glossary" ]; then
|
||||
local found
|
||||
found=$(find "$mdn_dir/glossary" -maxdepth 1 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
150
linux_configuration/scripts/single_use/utils/lib/docs_python.sh
Executable file
150
linux_configuration/scripts/single_use/utils/lib/docs_python.sh
Executable file
@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
# Python documentation lookup.
|
||||
#
|
||||
# Sourced by lookup_docs.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
#==============================================================================
|
||||
# Python-specific lookup
|
||||
#==============================================================================
|
||||
lookup_python() {
|
||||
local term="$1"
|
||||
local in_module="$2" # Optional: look for term within this module
|
||||
local doc_dir="$DOCS_DIR/python"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
# Normalize term (preserve case for True/False/None)
|
||||
local term_lower
|
||||
term_lower=$(echo "$term" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# If looking for a term within a specific module
|
||||
if [ -n "$in_module" ]; then
|
||||
local module_lower
|
||||
module_lower=$(echo "$in_module" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [ -f "$doc_dir/library/${module_lower}.html" ]; then
|
||||
# Find anchor for the specific item in the module
|
||||
local anchor
|
||||
anchor=$(grep -oP "id=\"[^\"]*${term}[^\"]*\"" "$doc_dir/library/${module_lower}.html" 2>/dev/null | head -1 | sed 's/id="//;s/"//')
|
||||
|
||||
if [ -n "$anchor" ]; then
|
||||
result="$doc_dir/library/${module_lower}.html#$anchor"
|
||||
desc="Python: $in_module.$term"
|
||||
else
|
||||
# Just link to the module
|
||||
result="$doc_dir/library/${module_lower}.html"
|
||||
desc="Python: $term in module $in_module"
|
||||
fi
|
||||
echo "$result|$desc"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local _kw
|
||||
_kw="$(python_keyword_doc "$doc_dir" "$term" "$term_lower")"
|
||||
IFS=$'\t' read -r result desc <<<"$_kw"
|
||||
|
||||
# Verify file exists for keyword lookups
|
||||
if [ -n "$result" ] && [ ! -f "${result%%#*}" ]; then
|
||||
result=""
|
||||
desc=""
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 2: Check if it's a module (pathlib, os, sys, etc.)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ] && [ -f "$doc_dir/library/${term_lower}.html" ]; then
|
||||
result="$doc_dir/library/${term_lower}.html"
|
||||
desc="Python module: $term"
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 3: Built-in functions (library/functions.html)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ] && [ -f "$doc_dir/library/functions.html" ]; then
|
||||
if grep -q "id=\"$term_lower\"" "$doc_dir/library/functions.html" 2>/dev/null; then
|
||||
result="$doc_dir/library/functions.html#$term_lower"
|
||||
desc="Python built-in function: $term"
|
||||
fi
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 4: Built-in types (library/stdtypes.html)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ]; then
|
||||
case "$term_lower" in
|
||||
str | string)
|
||||
result="$doc_dir/library/stdtypes.html#str"
|
||||
desc="Python: str type"
|
||||
;;
|
||||
int | integer)
|
||||
result="$doc_dir/library/stdtypes.html#int"
|
||||
desc="Python: int type"
|
||||
;;
|
||||
float)
|
||||
result="$doc_dir/library/stdtypes.html#float"
|
||||
desc="Python: float type"
|
||||
;;
|
||||
list)
|
||||
result="$doc_dir/library/stdtypes.html#list"
|
||||
desc="Python: list type"
|
||||
;;
|
||||
dict | dictionary)
|
||||
result="$doc_dir/library/stdtypes.html#dict"
|
||||
desc="Python: dict type"
|
||||
;;
|
||||
set)
|
||||
result="$doc_dir/library/stdtypes.html#set"
|
||||
desc="Python: set type"
|
||||
;;
|
||||
tuple)
|
||||
result="$doc_dir/library/stdtypes.html#tuple"
|
||||
desc="Python: tuple type"
|
||||
;;
|
||||
bool | boolean)
|
||||
result="$doc_dir/library/stdtypes.html#boolean-values"
|
||||
desc="Python: bool type"
|
||||
;;
|
||||
bytes)
|
||||
result="$doc_dir/library/stdtypes.html#bytes"
|
||||
desc="Python: bytes type"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 5: Check for class/function in module docs (exact id match)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ]; then
|
||||
local found_in
|
||||
# Look for exact id match first
|
||||
found_in=$(grep -l "id=\"$term\"" "$doc_dir/library/"*.html 2>/dev/null | head -1)
|
||||
if [ -n "$found_in" ]; then
|
||||
result="$found_in#$term"
|
||||
local module
|
||||
module=$(basename "$found_in" .html)
|
||||
desc="Python: $term in module $module"
|
||||
fi
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 6: Search in index
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ] && [ -f "$INDEX_DIR/python_index.txt" ]; then
|
||||
local index_match
|
||||
index_match=$(grep -i "^$term " "$INDEX_DIR/python_index.txt" 2>/dev/null | head -1)
|
||||
if [ -n "$index_match" ]; then
|
||||
result=$(echo "$index_match" | cut -d' ' -f2-)
|
||||
desc="Python: $term (from index)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# NO full-text search fallback - it produces garbage results
|
||||
# If we can't find a specific doc, return nothing (will fall back to online)
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
167
linux_configuration/scripts/single_use/utils/lib/docs_python_keywords.sh
Executable file
167
linux_configuration/scripts/single_use/utils/lib/docs_python_keywords.sh
Executable file
@ -0,0 +1,167 @@
|
||||
#!/bin/bash
|
||||
# Mapping Python keywords and operators to exact documentation anchors.
|
||||
#
|
||||
# Sourced by lookup_docs.sh; split out to keep docs_python.sh under the
|
||||
# 250-line cap. Emits "<result>\t<desc>" so the caller keeps its own
|
||||
# control flow; it reads doc_dir/term/term_lower from the caller's scope.
|
||||
|
||||
python_keyword_doc() {
|
||||
local doc_dir="$1" term="$2" term_lower="$3"
|
||||
local result="" desc=""
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 1: Python keywords - map to exact documentation locations
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
# Compound statements (reference/compound_stmts.html)
|
||||
case "$term_lower" in
|
||||
if | elif | else)
|
||||
result="$doc_dir/reference/compound_stmts.html#if"
|
||||
desc="Python: if statement"
|
||||
;;
|
||||
for)
|
||||
result="$doc_dir/reference/compound_stmts.html#for"
|
||||
desc="Python: for statement"
|
||||
;;
|
||||
while)
|
||||
result="$doc_dir/reference/compound_stmts.html#while"
|
||||
desc="Python: while statement"
|
||||
;;
|
||||
def)
|
||||
result="$doc_dir/reference/compound_stmts.html#def"
|
||||
desc="Python: function definition"
|
||||
;;
|
||||
class)
|
||||
result="$doc_dir/reference/compound_stmts.html#class"
|
||||
desc="Python: class definition"
|
||||
;;
|
||||
try | except | finally)
|
||||
result="$doc_dir/reference/compound_stmts.html#try"
|
||||
desc="Python: try statement"
|
||||
;;
|
||||
with)
|
||||
result="$doc_dir/reference/compound_stmts.html#with"
|
||||
desc="Python: with statement"
|
||||
;;
|
||||
async)
|
||||
result="$doc_dir/reference/compound_stmts.html#async"
|
||||
desc="Python: async definition"
|
||||
;;
|
||||
match | case)
|
||||
result="$doc_dir/reference/compound_stmts.html#match"
|
||||
desc="Python: match statement"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Simple statements (reference/simple_stmts.html)
|
||||
if [ -z "$result" ]; then
|
||||
case "$term_lower" in
|
||||
return)
|
||||
result="$doc_dir/reference/simple_stmts.html#return"
|
||||
desc="Python: return statement"
|
||||
;;
|
||||
pass)
|
||||
result="$doc_dir/reference/simple_stmts.html#pass"
|
||||
desc="Python: pass statement"
|
||||
;;
|
||||
break)
|
||||
result="$doc_dir/reference/simple_stmts.html#break"
|
||||
desc="Python: break statement"
|
||||
;;
|
||||
continue)
|
||||
result="$doc_dir/reference/simple_stmts.html#continue"
|
||||
desc="Python: continue statement"
|
||||
;;
|
||||
import | from)
|
||||
result="$doc_dir/reference/simple_stmts.html#import"
|
||||
desc="Python: import statement"
|
||||
;;
|
||||
raise)
|
||||
result="$doc_dir/reference/simple_stmts.html#raise"
|
||||
desc="Python: raise statement"
|
||||
;;
|
||||
assert)
|
||||
result="$doc_dir/reference/simple_stmts.html#assert"
|
||||
desc="Python: assert statement"
|
||||
;;
|
||||
yield)
|
||||
result="$doc_dir/reference/simple_stmts.html#yield"
|
||||
desc="Python: yield expression"
|
||||
;;
|
||||
del)
|
||||
result="$doc_dir/reference/simple_stmts.html#del"
|
||||
desc="Python: del statement"
|
||||
;;
|
||||
global)
|
||||
result="$doc_dir/reference/simple_stmts.html#global"
|
||||
desc="Python: global statement"
|
||||
;;
|
||||
nonlocal)
|
||||
result="$doc_dir/reference/simple_stmts.html#nonlocal"
|
||||
desc="Python: nonlocal statement"
|
||||
;;
|
||||
type)
|
||||
result="$doc_dir/reference/simple_stmts.html#type"
|
||||
desc="Python: type alias statement"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Expressions/operators (reference/expressions.html)
|
||||
if [ -z "$result" ]; then
|
||||
case "$term_lower" in
|
||||
and)
|
||||
result="$doc_dir/reference/expressions.html#and"
|
||||
desc="Python: and operator"
|
||||
;;
|
||||
or)
|
||||
result="$doc_dir/reference/expressions.html#or"
|
||||
desc="Python: or operator"
|
||||
;;
|
||||
not)
|
||||
result="$doc_dir/reference/expressions.html#not"
|
||||
desc="Python: not operator"
|
||||
;;
|
||||
in)
|
||||
result="$doc_dir/reference/expressions.html#in"
|
||||
desc="Python: in operator"
|
||||
;;
|
||||
is)
|
||||
result="$doc_dir/reference/expressions.html#is"
|
||||
desc="Python: is operator"
|
||||
;;
|
||||
lambda)
|
||||
result="$doc_dir/reference/expressions.html#lambda"
|
||||
desc="Python: lambda expression"
|
||||
;;
|
||||
await)
|
||||
result="$doc_dir/reference/expressions.html#await"
|
||||
desc="Python: await expression"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Built-in constants (library/constants.html) - case-sensitive!
|
||||
if [ -z "$result" ]; then
|
||||
case "$term" in
|
||||
True | False)
|
||||
result="$doc_dir/library/constants.html#$term"
|
||||
desc="Python: $term constant"
|
||||
;;
|
||||
None)
|
||||
result="$doc_dir/library/constants.html#None"
|
||||
desc="Python: None constant"
|
||||
;;
|
||||
Ellipsis)
|
||||
result="$doc_dir/library/constants.html#Ellipsis"
|
||||
desc="Python: Ellipsis constant"
|
||||
;;
|
||||
NotImplemented)
|
||||
result="$doc_dir/library/constants.html#NotImplemented"
|
||||
desc="Python: NotImplemented constant"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
printf '%s\t%s\n' "$result" "$desc"
|
||||
}
|
||||
98
linux_configuration/scripts/single_use/utils/lib/docs_rust.sh
Executable file
98
linux_configuration/scripts/single_use/utils/lib/docs_rust.sh
Executable file
@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# Rust documentation lookup.
|
||||
#
|
||||
# Sourced by lookup_docs.sh; split out to keep it under the 250-line
|
||||
# cap. Sourced rather than run, so it inherits the caller's strict mode
|
||||
# and the variables defined above the source line.
|
||||
|
||||
#==============================================================================
|
||||
# Rust specific lookup
|
||||
#==============================================================================
|
||||
lookup_rust() {
|
||||
local term="$1"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
if command -v rustup &>/dev/null; then
|
||||
# Use rustup doc to get path
|
||||
local rust_doc_path
|
||||
rust_doc_path=$(rustup doc --path 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
|
||||
|
||||
# Search in std docs
|
||||
if [ -d "$rust_doc_path/std" ]; then
|
||||
local found
|
||||
found=$(find "$rust_doc_path/std" -name "*${term}*" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$found" ]; then
|
||||
result="$found"
|
||||
desc="Rust: $term"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Go specific lookup
|
||||
#==============================================================================
|
||||
lookup_go() {
|
||||
local term="$1"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
if command -v go &>/dev/null; then
|
||||
# Check if it's a stdlib package
|
||||
if go doc "$term" &>/dev/null; then
|
||||
result="go doc $term"
|
||||
desc="Go package: $term (use 'go doc $term' to view)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Shell specific lookup
|
||||
#==============================================================================
|
||||
lookup_shell() {
|
||||
local term="$1"
|
||||
local doc_dir="$DOCS_DIR/shell"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
# Check bash builtins
|
||||
if [ -f "$doc_dir/bash_builtins.txt" ]; then
|
||||
if grep -q "=== $term ===" "$doc_dir/bash_builtins.txt" 2>/dev/null; then
|
||||
result="$doc_dir/bash_builtins.txt"
|
||||
desc="Bash builtin: $term"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check common commands
|
||||
if [ -z "$result" ] && [ -f "$doc_dir/common_commands.txt" ]; then
|
||||
if grep -q "^$term" "$doc_dir/common_commands.txt" 2>/dev/null; then
|
||||
local cmd_desc
|
||||
cmd_desc=$(grep "^$term" "$doc_dir/common_commands.txt" | head -1)
|
||||
result="$doc_dir/common_commands.txt"
|
||||
desc="Shell command: $cmd_desc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try man page
|
||||
if [ -z "$result" ]; then
|
||||
local man_path
|
||||
man_path=$(man -w "$term" 2>/dev/null)
|
||||
if [ -n "$man_path" ]; then
|
||||
result="man $term"
|
||||
desc="Manual page: $term (use 'man $term' to view)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
@ -35,793 +35,22 @@ else
|
||||
NC=''
|
||||
fi
|
||||
|
||||
#==============================================================================
|
||||
# Python-specific lookup
|
||||
#==============================================================================
|
||||
lookup_python() {
|
||||
local term="$1"
|
||||
local in_module="$2" # Optional: look for term within this module
|
||||
local doc_dir="$DOCS_DIR/python"
|
||||
local result=""
|
||||
local desc=""
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
|
||||
# shellcheck source=lib/docs_python_keywords.sh
|
||||
source "$SCRIPT_DIR/lib/docs_python_keywords.sh"
|
||||
# shellcheck source=lib/docs_python.sh
|
||||
source "$SCRIPT_DIR/lib/docs_python.sh"
|
||||
# shellcheck source=lib/docs_cpp.sh
|
||||
source "$SCRIPT_DIR/lib/docs_cpp.sh"
|
||||
# shellcheck source=lib/docs_js.sh
|
||||
source "$SCRIPT_DIR/lib/docs_js.sh"
|
||||
# shellcheck source=lib/docs_rust.sh
|
||||
source "$SCRIPT_DIR/lib/docs_rust.sh"
|
||||
# shellcheck source=lib/docs_all.sh
|
||||
source "$SCRIPT_DIR/lib/docs_all.sh"
|
||||
|
||||
# Normalize term (preserve case for True/False/None)
|
||||
local term_lower
|
||||
term_lower=$(echo "$term" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# If looking for a term within a specific module
|
||||
if [ -n "$in_module" ]; then
|
||||
local module_lower
|
||||
module_lower=$(echo "$in_module" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [ -f "$doc_dir/library/${module_lower}.html" ]; then
|
||||
# Find anchor for the specific item in the module
|
||||
local anchor
|
||||
anchor=$(grep -oP "id=\"[^\"]*${term}[^\"]*\"" "$doc_dir/library/${module_lower}.html" 2>/dev/null | head -1 | sed 's/id="//;s/"//')
|
||||
|
||||
if [ -n "$anchor" ]; then
|
||||
result="$doc_dir/library/${module_lower}.html#$anchor"
|
||||
desc="Python: $in_module.$term"
|
||||
else
|
||||
# Just link to the module
|
||||
result="$doc_dir/library/${module_lower}.html"
|
||||
desc="Python: $term in module $in_module"
|
||||
fi
|
||||
echo "$result|$desc"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 1: Python keywords - map to exact documentation locations
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
# Compound statements (reference/compound_stmts.html)
|
||||
case "$term_lower" in
|
||||
if | elif | else)
|
||||
result="$doc_dir/reference/compound_stmts.html#if"
|
||||
desc="Python: if statement"
|
||||
;;
|
||||
for)
|
||||
result="$doc_dir/reference/compound_stmts.html#for"
|
||||
desc="Python: for statement"
|
||||
;;
|
||||
while)
|
||||
result="$doc_dir/reference/compound_stmts.html#while"
|
||||
desc="Python: while statement"
|
||||
;;
|
||||
def)
|
||||
result="$doc_dir/reference/compound_stmts.html#def"
|
||||
desc="Python: function definition"
|
||||
;;
|
||||
class)
|
||||
result="$doc_dir/reference/compound_stmts.html#class"
|
||||
desc="Python: class definition"
|
||||
;;
|
||||
try | except | finally)
|
||||
result="$doc_dir/reference/compound_stmts.html#try"
|
||||
desc="Python: try statement"
|
||||
;;
|
||||
with)
|
||||
result="$doc_dir/reference/compound_stmts.html#with"
|
||||
desc="Python: with statement"
|
||||
;;
|
||||
async)
|
||||
result="$doc_dir/reference/compound_stmts.html#async"
|
||||
desc="Python: async definition"
|
||||
;;
|
||||
match | case)
|
||||
result="$doc_dir/reference/compound_stmts.html#match"
|
||||
desc="Python: match statement"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Simple statements (reference/simple_stmts.html)
|
||||
if [ -z "$result" ]; then
|
||||
case "$term_lower" in
|
||||
return)
|
||||
result="$doc_dir/reference/simple_stmts.html#return"
|
||||
desc="Python: return statement"
|
||||
;;
|
||||
pass)
|
||||
result="$doc_dir/reference/simple_stmts.html#pass"
|
||||
desc="Python: pass statement"
|
||||
;;
|
||||
break)
|
||||
result="$doc_dir/reference/simple_stmts.html#break"
|
||||
desc="Python: break statement"
|
||||
;;
|
||||
continue)
|
||||
result="$doc_dir/reference/simple_stmts.html#continue"
|
||||
desc="Python: continue statement"
|
||||
;;
|
||||
import | from)
|
||||
result="$doc_dir/reference/simple_stmts.html#import"
|
||||
desc="Python: import statement"
|
||||
;;
|
||||
raise)
|
||||
result="$doc_dir/reference/simple_stmts.html#raise"
|
||||
desc="Python: raise statement"
|
||||
;;
|
||||
assert)
|
||||
result="$doc_dir/reference/simple_stmts.html#assert"
|
||||
desc="Python: assert statement"
|
||||
;;
|
||||
yield)
|
||||
result="$doc_dir/reference/simple_stmts.html#yield"
|
||||
desc="Python: yield expression"
|
||||
;;
|
||||
del)
|
||||
result="$doc_dir/reference/simple_stmts.html#del"
|
||||
desc="Python: del statement"
|
||||
;;
|
||||
global)
|
||||
result="$doc_dir/reference/simple_stmts.html#global"
|
||||
desc="Python: global statement"
|
||||
;;
|
||||
nonlocal)
|
||||
result="$doc_dir/reference/simple_stmts.html#nonlocal"
|
||||
desc="Python: nonlocal statement"
|
||||
;;
|
||||
type)
|
||||
result="$doc_dir/reference/simple_stmts.html#type"
|
||||
desc="Python: type alias statement"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Expressions/operators (reference/expressions.html)
|
||||
if [ -z "$result" ]; then
|
||||
case "$term_lower" in
|
||||
and)
|
||||
result="$doc_dir/reference/expressions.html#and"
|
||||
desc="Python: and operator"
|
||||
;;
|
||||
or)
|
||||
result="$doc_dir/reference/expressions.html#or"
|
||||
desc="Python: or operator"
|
||||
;;
|
||||
not)
|
||||
result="$doc_dir/reference/expressions.html#not"
|
||||
desc="Python: not operator"
|
||||
;;
|
||||
in)
|
||||
result="$doc_dir/reference/expressions.html#in"
|
||||
desc="Python: in operator"
|
||||
;;
|
||||
is)
|
||||
result="$doc_dir/reference/expressions.html#is"
|
||||
desc="Python: is operator"
|
||||
;;
|
||||
lambda)
|
||||
result="$doc_dir/reference/expressions.html#lambda"
|
||||
desc="Python: lambda expression"
|
||||
;;
|
||||
await)
|
||||
result="$doc_dir/reference/expressions.html#await"
|
||||
desc="Python: await expression"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Built-in constants (library/constants.html) - case-sensitive!
|
||||
if [ -z "$result" ]; then
|
||||
case "$term" in
|
||||
True | False)
|
||||
result="$doc_dir/library/constants.html#$term"
|
||||
desc="Python: $term constant"
|
||||
;;
|
||||
None)
|
||||
result="$doc_dir/library/constants.html#None"
|
||||
desc="Python: None constant"
|
||||
;;
|
||||
Ellipsis)
|
||||
result="$doc_dir/library/constants.html#Ellipsis"
|
||||
desc="Python: Ellipsis constant"
|
||||
;;
|
||||
NotImplemented)
|
||||
result="$doc_dir/library/constants.html#NotImplemented"
|
||||
desc="Python: NotImplemented constant"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Verify file exists for keyword lookups
|
||||
if [ -n "$result" ] && [ ! -f "${result%%#*}" ]; then
|
||||
result=""
|
||||
desc=""
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 2: Check if it's a module (pathlib, os, sys, etc.)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ] && [ -f "$doc_dir/library/${term_lower}.html" ]; then
|
||||
result="$doc_dir/library/${term_lower}.html"
|
||||
desc="Python module: $term"
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 3: Built-in functions (library/functions.html)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ] && [ -f "$doc_dir/library/functions.html" ]; then
|
||||
if grep -q "id=\"$term_lower\"" "$doc_dir/library/functions.html" 2>/dev/null; then
|
||||
result="$doc_dir/library/functions.html#$term_lower"
|
||||
desc="Python built-in function: $term"
|
||||
fi
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 4: Built-in types (library/stdtypes.html)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ]; then
|
||||
case "$term_lower" in
|
||||
str | string)
|
||||
result="$doc_dir/library/stdtypes.html#str"
|
||||
desc="Python: str type"
|
||||
;;
|
||||
int | integer)
|
||||
result="$doc_dir/library/stdtypes.html#int"
|
||||
desc="Python: int type"
|
||||
;;
|
||||
float)
|
||||
result="$doc_dir/library/stdtypes.html#float"
|
||||
desc="Python: float type"
|
||||
;;
|
||||
list)
|
||||
result="$doc_dir/library/stdtypes.html#list"
|
||||
desc="Python: list type"
|
||||
;;
|
||||
dict | dictionary)
|
||||
result="$doc_dir/library/stdtypes.html#dict"
|
||||
desc="Python: dict type"
|
||||
;;
|
||||
set)
|
||||
result="$doc_dir/library/stdtypes.html#set"
|
||||
desc="Python: set type"
|
||||
;;
|
||||
tuple)
|
||||
result="$doc_dir/library/stdtypes.html#tuple"
|
||||
desc="Python: tuple type"
|
||||
;;
|
||||
bool | boolean)
|
||||
result="$doc_dir/library/stdtypes.html#boolean-values"
|
||||
desc="Python: bool type"
|
||||
;;
|
||||
bytes)
|
||||
result="$doc_dir/library/stdtypes.html#bytes"
|
||||
desc="Python: bytes type"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 5: Check for class/function in module docs (exact id match)
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ]; then
|
||||
local found_in
|
||||
# Look for exact id match first
|
||||
found_in=$(grep -l "id=\"$term\"" "$doc_dir/library/"*.html 2>/dev/null | head -1)
|
||||
if [ -n "$found_in" ]; then
|
||||
result="$found_in#$term"
|
||||
local module
|
||||
module=$(basename "$found_in" .html)
|
||||
desc="Python: $term in module $module"
|
||||
fi
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# PRIORITY 6: Search in index
|
||||
#--------------------------------------------------------------------------
|
||||
if [ -z "$result" ] && [ -f "$INDEX_DIR/python_index.txt" ]; then
|
||||
local index_match
|
||||
index_match=$(grep -i "^$term " "$INDEX_DIR/python_index.txt" 2>/dev/null | head -1)
|
||||
if [ -n "$index_match" ]; then
|
||||
result=$(echo "$index_match" | cut -d' ' -f2-)
|
||||
desc="Python: $term (from index)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# NO full-text search fallback - it produces garbage results
|
||||
# If we can't find a specific doc, return nothing (will fall back to online)
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# C/C++ specific lookup
|
||||
#==============================================================================
|
||||
lookup_cpp() {
|
||||
local term="$1"
|
||||
local doc_dir="$DOCS_DIR/c_cpp"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
# Resolve symlink if present (system package installs to c_cpp/system/)
|
||||
[ -L "$doc_dir/system" ] && doc_dir="$doc_dir/system"
|
||||
|
||||
# Common C headers
|
||||
case "$term" in
|
||||
stdio.h | stdio)
|
||||
[ -f "$doc_dir/reference/cstdio/index.html" ] && result="$doc_dir/reference/cstdio/index.html"
|
||||
[ -f "$doc_dir/en/c/io.html" ] && result="$doc_dir/en/c/io.html"
|
||||
desc="C standard I/O header"
|
||||
;;
|
||||
stdlib.h | stdlib)
|
||||
[ -f "$doc_dir/reference/cstdlib/index.html" ] && result="$doc_dir/reference/cstdlib/index.html"
|
||||
[ -f "$doc_dir/en/c/memory.html" ] && result="$doc_dir/en/c/memory.html"
|
||||
desc="C standard library header"
|
||||
;;
|
||||
string.h | cstring)
|
||||
[ -f "$doc_dir/reference/cstring/index.html" ] && result="$doc_dir/reference/cstring/index.html"
|
||||
desc="C string handling header"
|
||||
;;
|
||||
math.h | cmath)
|
||||
[ -f "$doc_dir/reference/cmath/index.html" ] && result="$doc_dir/reference/cmath/index.html"
|
||||
desc="C math header"
|
||||
;;
|
||||
esac
|
||||
|
||||
# C++ STL containers
|
||||
case "$term" in
|
||||
vector)
|
||||
[ -f "$doc_dir/reference/vector/index.html" ] && result="$doc_dir/reference/vector/index.html"
|
||||
[ -f "$doc_dir/en/cpp/container/vector.html" ] && result="$doc_dir/en/cpp/container/vector.html"
|
||||
desc="C++ std::vector container"
|
||||
;;
|
||||
map)
|
||||
[ -f "$doc_dir/reference/map/index.html" ] && result="$doc_dir/reference/map/index.html"
|
||||
desc="C++ std::map container"
|
||||
;;
|
||||
string)
|
||||
[ -f "$doc_dir/reference/string/index.html" ] && result="$doc_dir/reference/string/index.html"
|
||||
desc="C++ std::string"
|
||||
;;
|
||||
iostream)
|
||||
[ -f "$doc_dir/reference/iostream/index.html" ] && result="$doc_dir/reference/iostream/index.html"
|
||||
desc="C++ iostream header"
|
||||
;;
|
||||
esac
|
||||
|
||||
# C keywords
|
||||
case "$term" in
|
||||
if | else | for | while | do | switch | case | break | continue | return | goto)
|
||||
[ -f "$doc_dir/en/c/language/$term.html" ] && result="$doc_dir/en/c/language/$term.html"
|
||||
[ -f "$doc_dir/en/cpp/language/$term.html" ] && result="$doc_dir/en/cpp/language/$term.html"
|
||||
desc="C/C++ keyword: $term"
|
||||
;;
|
||||
int | char | float | double | void | long | short | unsigned | signed)
|
||||
[ -f "$doc_dir/en/c/language/type.html" ] && result="$doc_dir/en/c/language/type.html"
|
||||
desc="C/C++ type: $term"
|
||||
;;
|
||||
struct | union | enum | typedef)
|
||||
[ -f "$doc_dir/en/c/language/$term.html" ] && result="$doc_dir/en/c/language/$term.html"
|
||||
desc="C/C++ keyword: $term"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Search in files if not found (use -L to follow symlinks)
|
||||
if [ -z "$result" ]; then
|
||||
local found
|
||||
found=$(find -L "$doc_dir" -name "*${term}*" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$found" ]; then
|
||||
result="$found"
|
||||
desc="C/C++: $term"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# JavaScript/MDN specific lookup
|
||||
# Searches the cloned MDN content repository
|
||||
#==============================================================================
|
||||
lookup_js() {
|
||||
local term="$1"
|
||||
local mdn_dir="$DOCS_DIR/mdn-content/files/en-us"
|
||||
|
||||
# Normalize term for searching
|
||||
local term_lower
|
||||
term_lower=$(echo "$term" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Handle common statement aliases (MDN uses if...else, try...catch, etc.)
|
||||
local statement_aliases=(
|
||||
"if:if...else"
|
||||
"else:if...else"
|
||||
"try:try...catch"
|
||||
"catch:try...catch"
|
||||
"finally:try...catch"
|
||||
"do:do...while"
|
||||
"while:while"
|
||||
"for:for"
|
||||
"switch:switch"
|
||||
"case:switch"
|
||||
"default:switch"
|
||||
)
|
||||
|
||||
for alias in "${statement_aliases[@]}"; do
|
||||
local key="${alias%%:*}"
|
||||
local value="${alias##*:}"
|
||||
if [ "$term_lower" = "$key" ]; then
|
||||
local stmt_dir="$mdn_dir/web/javascript/reference/statements/$value"
|
||||
if [ -d "$stmt_dir" ] && [ -f "$stmt_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$stmt_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$stmt_dir/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle boolean/null literals
|
||||
case "$term_lower" in
|
||||
true | false)
|
||||
local bool_dir="$mdn_dir/web/javascript/reference/global_objects/boolean"
|
||||
if [ -d "$bool_dir" ] && [ -f "$bool_dir/index.md" ]; then
|
||||
echo "$bool_dir/index.md|Boolean ($term)"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
null)
|
||||
local null_dir="$mdn_dir/web/javascript/reference/operators/null"
|
||||
if [ -d "$null_dir" ] && [ -f "$null_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$null_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$null_dir/index.md|${title:-null}"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
undefined)
|
||||
local undef_dir="$mdn_dir/web/javascript/reference/global_objects/undefined"
|
||||
if [ -d "$undef_dir" ] && [ -f "$undef_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$undef_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$undef_dir/index.md|${title:-undefined}"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Search JavaScript reference directory structure (priority order)
|
||||
local search_dirs=(
|
||||
"$mdn_dir/web/javascript/reference/statements"
|
||||
"$mdn_dir/web/javascript/reference/operators"
|
||||
"$mdn_dir/web/javascript/reference/global_objects"
|
||||
"$mdn_dir/web/javascript/reference/functions"
|
||||
"$mdn_dir/web/javascript/reference/classes"
|
||||
)
|
||||
|
||||
for search_dir in "${search_dirs[@]}"; do
|
||||
if [ -d "$search_dir" ]; then
|
||||
# Look for exact directory match (MDN uses directories with index.md)
|
||||
local found_dir
|
||||
found_dir=$(find "$search_dir" -maxdepth 2 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found_dir" ] && [ -f "$found_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found_dir/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Search Web APIs - prioritize *_api directories for common terms
|
||||
if [ -d "$mdn_dir/web/api" ]; then
|
||||
# First try <term>_api directory (e.g., fetch_api, console_api)
|
||||
local api_dir="$mdn_dir/web/api/${term_lower}_api"
|
||||
if [ -d "$api_dir" ] && [ -f "$api_dir/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$api_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$api_dir/index.md|${title:-$term API}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Then try exact top-level API interface (e.g., Console, Document, Element)
|
||||
local found
|
||||
found=$(find "$mdn_dir/web/api" -maxdepth 1 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try window/<term> for global functions like alert, confirm, etc.
|
||||
local window_method="$mdn_dir/web/api/window/${term_lower}"
|
||||
if [ -d "$window_method" ] && [ -f "$window_method/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$window_method/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$window_method/index.md|${title:-Window.$term()}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Search nested API methods
|
||||
found=$(find "$mdn_dir/web/api" -maxdepth 3 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Now try partial matches in Global Objects (e.g., Array.from, Object.keys)
|
||||
if [ -d "$mdn_dir/web/javascript/reference/global_objects" ]; then
|
||||
local found
|
||||
found=$(find "$mdn_dir/web/javascript/reference/global_objects" -maxdepth 2 -type d -iname "*${term}*" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Glossary as last resort
|
||||
if [ -d "$mdn_dir/glossary" ]; then
|
||||
local found
|
||||
found=$(find "$mdn_dir/glossary" -maxdepth 1 -type d -iname "$term" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
|
||||
local title
|
||||
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
|
||||
echo "$found/index.md|${title:-$term}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Rust specific lookup
|
||||
#==============================================================================
|
||||
lookup_rust() {
|
||||
local term="$1"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
if command -v rustup &>/dev/null; then
|
||||
# Use rustup doc to get path
|
||||
local rust_doc_path
|
||||
rust_doc_path=$(rustup doc --path 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
|
||||
|
||||
# Search in std docs
|
||||
if [ -d "$rust_doc_path/std" ]; then
|
||||
local found
|
||||
found=$(find "$rust_doc_path/std" -name "*${term}*" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$found" ]; then
|
||||
result="$found"
|
||||
desc="Rust: $term"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Go specific lookup
|
||||
#==============================================================================
|
||||
lookup_go() {
|
||||
local term="$1"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
if command -v go &>/dev/null; then
|
||||
# Check if it's a stdlib package
|
||||
if go doc "$term" &>/dev/null; then
|
||||
result="go doc $term"
|
||||
desc="Go package: $term (use 'go doc $term' to view)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Shell specific lookup
|
||||
#==============================================================================
|
||||
lookup_shell() {
|
||||
local term="$1"
|
||||
local doc_dir="$DOCS_DIR/shell"
|
||||
local result=""
|
||||
local desc=""
|
||||
|
||||
# Check bash builtins
|
||||
if [ -f "$doc_dir/bash_builtins.txt" ]; then
|
||||
if grep -q "=== $term ===" "$doc_dir/bash_builtins.txt" 2>/dev/null; then
|
||||
result="$doc_dir/bash_builtins.txt"
|
||||
desc="Bash builtin: $term"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check common commands
|
||||
if [ -z "$result" ] && [ -f "$doc_dir/common_commands.txt" ]; then
|
||||
if grep -q "^$term" "$doc_dir/common_commands.txt" 2>/dev/null; then
|
||||
local cmd_desc
|
||||
cmd_desc=$(grep "^$term" "$doc_dir/common_commands.txt" | head -1)
|
||||
result="$doc_dir/common_commands.txt"
|
||||
desc="Shell command: $cmd_desc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try man page
|
||||
if [ -z "$result" ]; then
|
||||
local man_path
|
||||
man_path=$(man -w "$term" 2>/dev/null)
|
||||
if [ -n "$man_path" ]; then
|
||||
result="man $term"
|
||||
desc="Manual page: $term (use 'man $term' to view)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result|$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Generic lookup (searches all languages)
|
||||
#==============================================================================
|
||||
lookup_all() {
|
||||
local term="$1"
|
||||
|
||||
# Try each language
|
||||
for lang in python cpp js rust go shell; do
|
||||
local result
|
||||
result=$(lookup_$lang "$term" 2>/dev/null)
|
||||
if [ -n "$result" ]; then
|
||||
echo "$lang: $result"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Parse Python import and lookup the actual imported item
|
||||
#==============================================================================
|
||||
parse_python_import() {
|
||||
local import_line="$1"
|
||||
|
||||
# Handle "from X import Y" format
|
||||
if [[ $import_line =~ ^from[[:space:]]+([^[:space:]]+)[[:space:]]+import[[:space:]]+(.+) ]]; then
|
||||
local module="${BASH_REMATCH[1]}"
|
||||
local items="${BASH_REMATCH[2]}"
|
||||
|
||||
# Clean up items (remove parentheses, commas, etc.)
|
||||
items=$(echo "$items" | sed 's/[(),]//g' | awk '{print $1}')
|
||||
|
||||
# Output: module and first imported item
|
||||
echo "$module|$items"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Handle "import X" format
|
||||
if [[ $import_line =~ ^import[[:space:]]+([^[:space:],]+) ]]; then
|
||||
local module="${BASH_REMATCH[1]}"
|
||||
echo "$module|"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Smart lookup for imports
|
||||
#==============================================================================
|
||||
lookup_import() {
|
||||
local import_line="$1"
|
||||
local lang="$2"
|
||||
|
||||
case "$lang" in
|
||||
python)
|
||||
local parsed
|
||||
parsed=$(parse_python_import "$import_line")
|
||||
if [ -n "$parsed" ]; then
|
||||
local module item
|
||||
module=$(echo "$parsed" | cut -d'|' -f1)
|
||||
item=$(echo "$parsed" | cut -d'|' -f2)
|
||||
|
||||
# For "from X import Y", look up Y within module X's documentation
|
||||
if [ -n "$item" ] && [ -n "$module" ]; then
|
||||
local result
|
||||
# Pass both item and module to lookup_python
|
||||
result=$(lookup_python "$item" "$module")
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fall back to module documentation
|
||||
lookup_python "$module"
|
||||
fi
|
||||
;;
|
||||
|
||||
c_cpp)
|
||||
# Extract header name from #include <header> or #include "header"
|
||||
local header
|
||||
header=$(echo "$import_line" | sed -E 's/#include\s*[<"]([^">]+)[">]/\1/' | sed 's/\.h$//')
|
||||
lookup_cpp "$header"
|
||||
;;
|
||||
|
||||
javascript | typescript)
|
||||
# Extract module from import/require
|
||||
local module=""
|
||||
# Match: from "module" or from 'module'
|
||||
module=$(echo "$import_line" | grep -oP "from\s+['\"]\\K[^'\"]+")
|
||||
if [ -z "$module" ]; then
|
||||
# Match: require("module") or require('module')
|
||||
module=$(echo "$import_line" | grep -oP "require\\(['\"]\\K[^'\"]+")
|
||||
fi
|
||||
[ -n "$module" ] && lookup_js "$module"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown language: $lang"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Extract documentation content
|
||||
#==============================================================================
|
||||
extract_doc_content() {
|
||||
local file="$1"
|
||||
local term="$2"
|
||||
local max_lines="${3:-20}"
|
||||
|
||||
if [[ $file == *.html ]]; then
|
||||
# Extract text from HTML, find section about term
|
||||
if command -v html2text &>/dev/null; then
|
||||
html2text "$file" 2>/dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
|
||||
elif command -v lynx &>/dev/null; then
|
||||
lynx -dump -nolist "$file" 2>/dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
|
||||
else
|
||||
# Basic extraction
|
||||
sed 's/<[^>]*>//g' "$file" | grep -A"$max_lines" -i "$term" | head -"$max_lines"
|
||||
fi
|
||||
elif [[ $file == *.json ]]; then
|
||||
# Pretty print JSON section
|
||||
grep -A5 "\"$term\"" "$file" 2>/dev/null
|
||||
else
|
||||
# Plain text
|
||||
grep -A"$max_lines" -i "$term" "$file" | head -"$max_lines"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Main
|
||||
#==============================================================================
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <term> [language] [options]
|
||||
|
||||
Search offline documentation for a term.
|
||||
|
||||
Languages: python, cpp, c_cpp, js, javascript, rust, go, shell, all
|
||||
|
||||
Options:
|
||||
--open Open the documentation file (requires xdg-open)
|
||||
--extract Extract and display relevant content
|
||||
--import Parse and lookup an import statement
|
||||
--batch Process multiple terms from a file
|
||||
|
||||
Examples:
|
||||
$0 Path python # Find Path in Python docs
|
||||
$0 vector cpp # Find vector in C++ docs
|
||||
$0 map # Find map in all languages
|
||||
$0 --import "from pathlib import Path" python
|
||||
$0 --batch imports.txt python
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
if [ $# -eq 0 ]; then
|
||||
|
||||
@ -89,914 +89,19 @@ require_non_root() {
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $SCRIPT_NAME [OPTIONS] [COMMAND]
|
||||
|
||||
Root BL9000 phone from Arch Linux using Magisk.
|
||||
|
||||
Commands:
|
||||
install-deps Install required dependencies (adb, fastboot, tools)
|
||||
install-mtk Install MTKClient for MediaTek boot extraction
|
||||
check Check device connection and prerequisites
|
||||
backup Backup phone data before unlocking bootloader
|
||||
unlock Unlock bootloader (WARNING: wipes all data!)
|
||||
extract-mtk Extract boot.img using MTKClient (for MediaTek devices)
|
||||
patch Patch boot.img with Magisk (manual step on phone)
|
||||
flash [IMG] Flash patched boot image (uses magisk_patched.img or specified file)
|
||||
auto-root Automated: extract -> patch -> flash in one command
|
||||
root Extract boot, patch with Magisk, and flash
|
||||
full Run complete rooting process (deps + unlock + root)
|
||||
clean Remove temporary working directory
|
||||
help Show this message
|
||||
|
||||
Options:
|
||||
-h, --help Show this message
|
||||
--work-dir DIR Set working directory (default: $WORK_DIR)
|
||||
--boot-img FILE Use existing boot.img instead of extracting from device
|
||||
|
||||
Examples:
|
||||
$SCRIPT_NAME install-deps # Install required tools
|
||||
$SCRIPT_NAME install-mtk # Install MTKClient for boot extraction
|
||||
$SCRIPT_NAME check # Verify device connection
|
||||
$SCRIPT_NAME backup # Backup phone data first!
|
||||
$SCRIPT_NAME extract-mtk # Extract boot.img with MTKClient
|
||||
$SCRIPT_NAME auto-root # Automated rooting (extract + patch + flash)
|
||||
$SCRIPT_NAME full # Complete rooting process
|
||||
$SCRIPT_NAME root # Root only (assumes bootloader unlocked)
|
||||
|
||||
WARNING: Unlocking the bootloader will ERASE ALL DATA on your phone!
|
||||
Make sure to back up important data before proceeding.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
install_dependencies() {
|
||||
print_header "Installing Dependencies"
|
||||
|
||||
local packages=()
|
||||
local missing=()
|
||||
|
||||
# Check for required commands
|
||||
if ! command -v adb > /dev/null 2>&1; then
|
||||
packages+=("android-tools")
|
||||
missing+=("adb")
|
||||
fi
|
||||
|
||||
if ! command -v fastboot > /dev/null 2>&1 && ! pacman -Q android-tools > /dev/null 2>&1; then
|
||||
packages+=("android-tools")
|
||||
missing+=("fastboot")
|
||||
fi
|
||||
|
||||
if ! command -v unzip > /dev/null 2>&1; then
|
||||
packages+=("unzip")
|
||||
missing+=("unzip")
|
||||
fi
|
||||
|
||||
if ! command -v curl > /dev/null 2>&1; then
|
||||
packages+=("curl")
|
||||
missing+=("curl")
|
||||
fi
|
||||
|
||||
if ! command -v python3 > /dev/null 2>&1; then
|
||||
packages+=("python")
|
||||
missing+=("python3")
|
||||
fi
|
||||
|
||||
if ! command -v git > /dev/null 2>&1; then
|
||||
packages+=("git")
|
||||
missing+=("git")
|
||||
fi
|
||||
|
||||
# Check for libusb and fuse2 (needed for mtkclient)
|
||||
if ! pacman -Q libusb > /dev/null 2>&1; then
|
||||
packages+=("libusb")
|
||||
missing+=("libusb")
|
||||
fi
|
||||
|
||||
if ! pacman -Q fuse2 > /dev/null 2>&1; then
|
||||
packages+=("fuse2")
|
||||
missing+=("fuse2")
|
||||
fi
|
||||
|
||||
# Check for python-protobuf (needed for boot image tools)
|
||||
if ! python3 -c "import google.protobuf" 2> /dev/null; then
|
||||
packages+=("python-protobuf")
|
||||
missing+=("python-protobuf")
|
||||
fi
|
||||
|
||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||
log "All dependencies are already installed."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Missing dependencies: ${missing[*]}"
|
||||
|
||||
# Remove duplicates
|
||||
readarray -t packages < <(printf '%s\n' "${packages[@]}" | sort -u)
|
||||
|
||||
if ! confirm "Install missing packages: ${packages[*]}?"; then
|
||||
die "Cannot proceed without required dependencies."
|
||||
fi
|
||||
|
||||
log "Installing packages: ${packages[*]}"
|
||||
sudo pacman -S --needed --noconfirm "${packages[@]}" || die "Failed to install dependencies"
|
||||
|
||||
log "Dependencies installed successfully."
|
||||
}
|
||||
|
||||
setup_udev_rules() {
|
||||
print_header "Setting Up USB Access"
|
||||
|
||||
local udev_file="/etc/udev/rules.d/51-android.rules"
|
||||
local mtk_udev_dir="${WORK_DIR}/mtkclient/mtkclient/Setup/Linux"
|
||||
|
||||
# Install MTKClient udev rules if mtkclient is present
|
||||
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
|
||||
log "Installing MTKClient udev rules..."
|
||||
if [[ -d $mtk_udev_dir ]]; then
|
||||
sudo cp "$mtk_udev_dir"/*.rules /etc/udev/rules.d/ 2> /dev/null || warn "Failed to copy MTKClient rules"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f $udev_file ]]; then
|
||||
log "Android udev rules already exist at $udev_file"
|
||||
else
|
||||
if ! confirm "Create udev rules for Android device access?"; then
|
||||
warn "Skipping udev rules. You may need to run commands with sudo."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Creating Android udev rules..."
|
||||
|
||||
# Create comprehensive udev rules for Android devices
|
||||
sudo tee "$udev_file" > /dev/null << 'EOF'
|
||||
# Android Debug Bridge (ADB) devices
|
||||
# Add your device's vendor ID if not listed
|
||||
|
||||
# Google
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="adbusers"
|
||||
# MediaTek (common in BL9000)
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", MODE="0666", GROUP="adbusers"
|
||||
# Generic catch-all for Android devices
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="*", ATTR{idProduct}=="*", MODE="0666", GROUP="adbusers", SYMLINK+="android%n"
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Create adbusers group if it doesn't exist
|
||||
if ! getent group adbusers > /dev/null; then
|
||||
sudo groupadd -r adbusers
|
||||
log "Created adbusers group"
|
||||
fi
|
||||
|
||||
# Add current user to adbusers and plugdev groups
|
||||
if ! groups "$USER" | grep -q '\badbusers\b'; then
|
||||
sudo usermod -aG adbusers "$USER"
|
||||
log "Added $USER to adbusers group"
|
||||
fi
|
||||
|
||||
if ! getent group plugdev > /dev/null; then
|
||||
sudo groupadd -r plugdev
|
||||
fi
|
||||
|
||||
if ! groups "$USER" | grep -q '\bplugdev\b'; then
|
||||
sudo usermod -aG plugdev "$USER"
|
||||
log "Added $USER to plugdev group"
|
||||
fi
|
||||
|
||||
if ! getent group dialout > /dev/null; then
|
||||
sudo groupadd -r dialout
|
||||
fi
|
||||
|
||||
if ! groups "$USER" | grep -q '\bdialout\b'; then
|
||||
sudo usermod -aG dialout "$USER"
|
||||
log "Added $USER to dialout group"
|
||||
warn "You need to log out and back in for group membership to take effect."
|
||||
warn "Alternatively, run: newgrp dialout"
|
||||
fi
|
||||
|
||||
# Reload udev rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
|
||||
log "USB access configured successfully."
|
||||
}
|
||||
|
||||
backup_device_data() {
|
||||
print_header "Backing Up Device Data"
|
||||
|
||||
local backup_dir
|
||||
backup_dir="${WORK_DIR}/backup_$(date +%Y%m%d_%H%M%S)"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
log "Backup directory: $backup_dir" # Check device connection first
|
||||
if ! adb get-state > /dev/null 2>&1; then
|
||||
error "Device not connected. Please connect your device first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Starting comprehensive backup process..."
|
||||
|
||||
# 1. Backup internal storage (DCIM, Pictures, Documents, Downloads, etc.)
|
||||
log "Backing up internal storage (this may take a while)..."
|
||||
local storage_dirs=("DCIM" "Pictures" "Documents" "Download" "Music" "Movies" "WhatsApp" "Telegram")
|
||||
|
||||
for dir in "${storage_dirs[@]}"; do
|
||||
if adb shell "[ -d /sdcard/$dir ]" 2> /dev/null; then
|
||||
log " → Backing up /sdcard/$dir..."
|
||||
if adb pull "/sdcard/$dir" "$backup_dir/$dir" 2>&1 | grep -v "^$"; then
|
||||
log " ✓ $dir backed up successfully"
|
||||
else
|
||||
warn " ⚠ Could not backup $dir (may be empty or inaccessible)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. Backup SMS/MMS (if possible)
|
||||
log "Backing up SMS/MMS database..."
|
||||
if adb shell "su -c 'cp /data/data/com.android.providers.telephony/databases/mmssms.db /sdcard/mmssms.db'" 2> /dev/null; then
|
||||
adb pull /sdcard/mmssms.db "$backup_dir/mmssms.db" 2> /dev/null && log " ✓ SMS/MMS backed up"
|
||||
adb shell "rm /sdcard/mmssms.db" 2> /dev/null || true
|
||||
else
|
||||
warn " ⚠ SMS/MMS backup requires root (skipping)"
|
||||
fi
|
||||
|
||||
# 3. Backup contacts
|
||||
log "Backing up contacts..."
|
||||
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/contacts2.db /sdcard/contacts2.db'" 2> /dev/null; then
|
||||
adb pull /sdcard/contacts2.db "$backup_dir/contacts2.db" 2> /dev/null && log " ✓ Contacts backed up"
|
||||
adb shell "rm /sdcard/contacts2.db" 2> /dev/null || true
|
||||
else
|
||||
warn " ⚠ Contacts backup requires root (skipping)"
|
||||
fi
|
||||
|
||||
# 4. Backup call logs
|
||||
log "Backing up call logs..."
|
||||
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/calllog.db /sdcard/calllog.db'" 2> /dev/null; then
|
||||
adb pull /sdcard/calllog.db "$backup_dir/calllog.db" 2> /dev/null && log " ✓ Call logs backed up"
|
||||
adb shell "rm /sdcard/calllog.db" 2> /dev/null || true
|
||||
else
|
||||
warn " ⚠ Call logs backup requires root (skipping)"
|
||||
fi
|
||||
|
||||
# 5. Backup app list
|
||||
log "Backing up installed apps list..."
|
||||
adb shell "pm list packages -f" > "$backup_dir/installed_apps.txt"
|
||||
log " ✓ App list saved to installed_apps.txt"
|
||||
|
||||
# 6. Backup APKs for user-installed apps (optional, can be large)
|
||||
if confirm "Backup APK files for installed apps? (This can take a long time and use lots of space)"; then
|
||||
log "Backing up user-installed APKs..."
|
||||
local apk_dir="$backup_dir/apks"
|
||||
mkdir -p "$apk_dir"
|
||||
|
||||
# Get user-installed packages
|
||||
local user_apps
|
||||
user_apps=$(adb shell "pm list packages -3 -f" | sed 's/package://' | cut -d'=' -f2)
|
||||
|
||||
local count=0
|
||||
while IFS= read -r pkg; do
|
||||
if [[ -n $pkg ]]; then
|
||||
log " → Backing up $pkg..."
|
||||
local apk_path
|
||||
apk_path=$(adb shell "pm path $pkg" | head -n1 | sed 's/package://')
|
||||
if [[ -n $apk_path ]]; then
|
||||
adb pull "$apk_path" "$apk_dir/${pkg}.apk" > /dev/null 2>&1 && count=$((count + 1))
|
||||
fi
|
||||
fi
|
||||
done <<< "$user_apps"
|
||||
|
||||
log " ✓ Backed up $count APK files"
|
||||
fi
|
||||
|
||||
# 7. Full ADB backup (app data, if device supports it)
|
||||
log "Creating full ADB backup (app data)..."
|
||||
if confirm "Create full ADB backup? (You'll need to confirm on your device)"; then
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " On your phone: Tap 'Back up my data' when prompted"
|
||||
echo " You can set a password or leave it blank"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
|
||||
if adb backup -apk -shared -all -system -f "$backup_dir/full_backup.ab"; then
|
||||
log " ✓ Full ADB backup completed"
|
||||
log " Note: Restore with: adb restore full_backup.ab"
|
||||
else
|
||||
warn " ⚠ ADB backup failed or was cancelled"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8. Backup device info
|
||||
log "Saving device information..."
|
||||
{
|
||||
echo "Device Backup Information"
|
||||
echo "========================="
|
||||
echo "Date: $(date)"
|
||||
echo
|
||||
echo "Device Model: $(adb shell getprop ro.product.model | tr -d '\r\n')"
|
||||
echo "Android Version: $(adb shell getprop ro.build.version.release | tr -d '\r\n')"
|
||||
echo "Build Number: $(adb shell getprop ro.build.display.id | tr -d '\r\n')"
|
||||
echo "Security Patch: $(adb shell getprop ro.build.version.security_patch | tr -d '\r\n')"
|
||||
echo "Serial: $(adb shell getprop ro.serialno | tr -d '\r\n')"
|
||||
echo
|
||||
echo "Installed Apps:"
|
||||
adb shell "pm list packages -3" | sed 's/package:/ - /'
|
||||
} > "$backup_dir/device_info.txt"
|
||||
|
||||
log " ✓ Device info saved"
|
||||
|
||||
# Summary
|
||||
local backup_size
|
||||
backup_size=$(du -sh "$backup_dir" 2> /dev/null | cut -f1 || echo "unknown")
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ Backup Completed Successfully! ║${NC}"
|
||||
echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}"
|
||||
echo
|
||||
log "Backup location: $backup_dir"
|
||||
log "Backup size: $backup_size"
|
||||
echo
|
||||
echo "What was backed up:"
|
||||
echo " ✓ Photos (DCIM)"
|
||||
echo " ✓ Pictures"
|
||||
echo " ✓ Documents"
|
||||
echo " ✓ Downloads"
|
||||
echo " ✓ Music"
|
||||
echo " ✓ Movies"
|
||||
echo " ✓ WhatsApp data (if present)"
|
||||
echo " ✓ Telegram data (if present)"
|
||||
echo " ✓ Installed apps list"
|
||||
echo " ✓ Device information"
|
||||
if [[ -f "$backup_dir/full_backup.ab" ]]; then
|
||||
echo " ✓ Full app data backup"
|
||||
fi
|
||||
if [[ -d "$backup_dir/apks" ]]; then
|
||||
echo " ✓ APK files"
|
||||
fi
|
||||
echo
|
||||
log "Keep this backup safe! You'll need it to restore your data after rooting."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_device() {
|
||||
print_header "Checking Device Connection"
|
||||
|
||||
log "Starting ADB server..."
|
||||
adb start-server > /dev/null 2>&1 || true
|
||||
|
||||
log "Waiting for device..."
|
||||
if ! adb wait-for-device; then
|
||||
error "Failed to detect device via ADB."
|
||||
echo
|
||||
echo "Troubleshooting steps:"
|
||||
echo "1. Make sure USB debugging is enabled on your phone"
|
||||
echo " Settings → About Phone → Tap Build Number 7 times"
|
||||
echo " Settings → Developer Options → Enable USB Debugging"
|
||||
echo "2. Connect your phone via USB cable"
|
||||
echo "3. Accept the 'Allow USB debugging' prompt on your phone"
|
||||
echo "4. Run: adb devices"
|
||||
echo
|
||||
return 1
|
||||
fi
|
||||
|
||||
local device_info
|
||||
device_info=$(adb devices -l | grep -v "List of devices" | grep -v "^$" | head -n1)
|
||||
|
||||
if [[ -z $device_info ]]; then
|
||||
error "No device detected"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Device connected: $device_info"
|
||||
|
||||
# Check device properties
|
||||
local model
|
||||
model=$(adb shell getprop ro.product.model 2> /dev/null | tr -d '\r\n' || echo "Unknown")
|
||||
log "Model: $model"
|
||||
|
||||
local android_version
|
||||
android_version=$(adb shell getprop ro.build.version.release 2> /dev/null | tr -d '\r\n' || echo "Unknown")
|
||||
log "Android version: $android_version"
|
||||
|
||||
local battery_level
|
||||
battery_level=$(adb shell dumpsys battery | grep level | awk '{print $2}' | tr -d '\r\n' || echo "Unknown")
|
||||
log "Battery level: ${battery_level}%"
|
||||
|
||||
if [[ $battery_level != "Unknown" && $battery_level -lt 50 ]]; then
|
||||
warn "Battery level is below 50%. Charge your phone before proceeding."
|
||||
if ! confirm "Continue anyway?"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if bootloader is unlocked
|
||||
local unlock_status
|
||||
unlock_status=$(adb shell getprop ro.boot.verifiedbootstate 2> /dev/null | tr -d '\r\n' || echo "unknown")
|
||||
if [[ $unlock_status == "orange" || $unlock_status == "red" ]]; then
|
||||
log "Bootloader unlock status: ${GREEN}UNLOCKED${NC}"
|
||||
else
|
||||
warn "Bootloader appears to be LOCKED. You'll need to unlock it to root."
|
||||
fi
|
||||
|
||||
# Check if OEM unlocking is enabled
|
||||
local oem_unlock
|
||||
oem_unlock=$(adb shell getprop sys.oem_unlock_allowed 2> /dev/null | tr -d '\r\n' || echo "unknown")
|
||||
if [[ $oem_unlock == "1" ]]; then
|
||||
log "OEM unlocking: ${GREEN}ENABLED${NC}"
|
||||
else
|
||||
warn "OEM unlocking is not enabled in Developer Options."
|
||||
echo "Enable it at: Settings → Developer Options → OEM unlocking"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
unlock_bootloader() {
|
||||
print_header "Unlocking Bootloader"
|
||||
|
||||
echo
|
||||
echo -e "${RED}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${RED}║ WARNING ║${NC}"
|
||||
echo -e "${RED}║ ║${NC}"
|
||||
echo -e "${RED}║ Unlocking the bootloader will ERASE ALL DATA on your phone! ║${NC}"
|
||||
echo -e "${RED}║ ║${NC}"
|
||||
echo -e "${RED}║ This includes: ║${NC}"
|
||||
echo -e "${RED}║ - All apps and app data ║${NC}"
|
||||
echo -e "${RED}║ - Photos, videos, and files ║${NC}"
|
||||
echo -e "${RED}║ - System settings ║${NC}"
|
||||
echo -e "${RED}║ - Everything else on internal storage ║${NC}"
|
||||
echo -e "${RED}║ ║${NC}"
|
||||
echo -e "${RED}║ Make sure you have backed up important data! ║${NC}"
|
||||
echo -e "${RED}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo
|
||||
|
||||
if ! confirm "Have you backed up all important data and want to proceed?"; then
|
||||
log "Bootloader unlock cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! confirm "Are you ABSOLUTELY SURE? This cannot be undone!"; then
|
||||
log "Bootloader unlock cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Rebooting device to bootloader..."
|
||||
adb reboot bootloader || die "Failed to reboot to bootloader"
|
||||
|
||||
log "Waiting for fastboot mode..."
|
||||
sleep 5
|
||||
|
||||
if ! fastboot devices | grep -q .; then
|
||||
error "Device not detected in fastboot mode."
|
||||
echo
|
||||
echo "If the device doesn't enter fastboot automatically:"
|
||||
echo "1. Power off the phone completely"
|
||||
echo "2. Hold Volume Down + Power buttons simultaneously"
|
||||
echo "3. Release when you see the bootloader/fastboot screen"
|
||||
echo "4. Run: fastboot devices"
|
||||
echo
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Device in fastboot mode"
|
||||
|
||||
# Check current bootloader status
|
||||
local bl_status
|
||||
bl_status=$(fastboot getvar unlocked 2>&1 | grep "unlocked:" | awk '{print $2}' || echo "unknown")
|
||||
if [[ $bl_status == "yes" ]]; then
|
||||
log "Bootloader is already unlocked."
|
||||
fastboot reboot
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Attempting to unlock bootloader..."
|
||||
|
||||
# Try different unlock commands (varies by device)
|
||||
if fastboot flashing unlock 2>&1 | grep -qi "okay\|finished"; then
|
||||
log "Bootloader unlock command sent successfully."
|
||||
elif fastboot oem unlock 2>&1 | grep -qi "okay\|finished"; then
|
||||
log "Bootloader unlock command sent successfully."
|
||||
else
|
||||
error "Bootloader unlock command may have failed."
|
||||
echo
|
||||
echo "On your phone:"
|
||||
echo "1. Use volume buttons to select 'Unlock the bootloader'"
|
||||
echo "2. Press power button to confirm"
|
||||
echo
|
||||
|
||||
if ! confirm "Did you complete the unlock on the device?"; then
|
||||
fastboot reboot
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Rebooting device..."
|
||||
fastboot reboot || true
|
||||
|
||||
log "Bootloader unlocked successfully!"
|
||||
log "Device will now boot up and perform factory reset..."
|
||||
log "Waiting for device to come back online..."
|
||||
|
||||
sleep 10
|
||||
adb wait-for-device || true
|
||||
|
||||
log "Please complete the initial setup on your phone, then re-enable USB debugging."
|
||||
echo
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
download_magisk() {
|
||||
print_header "Downloading Magisk"
|
||||
|
||||
local magisk_apk="$WORK_DIR/magisk.apk"
|
||||
|
||||
if [[ -f $magisk_apk ]]; then
|
||||
log "Magisk APK already downloaded at $magisk_apk"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Downloading latest Magisk APK..."
|
||||
if ! curl -L -o "$magisk_apk" "$MAGISK_APK_URL"; then
|
||||
error "Failed to download Magisk APK"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Magisk downloaded successfully: $magisk_apk"
|
||||
return 0
|
||||
}
|
||||
|
||||
install_mtkclient() {
|
||||
print_header "Installing MTKClient"
|
||||
|
||||
local mtk_dir="${WORK_DIR}/mtkclient"
|
||||
|
||||
if [[ -d $mtk_dir && -f "$mtk_dir/mtk.py" ]]; then
|
||||
log "MTKClient already installed at $mtk_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! confirm "Install MTKClient for MediaTek boot image extraction?"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Cloning MTKClient repository..."
|
||||
if ! git clone https://github.com/bkerler/mtkclient "$mtk_dir"; then
|
||||
error "Failed to clone MTKClient"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Installing MTKClient Python dependencies..."
|
||||
cd "$mtk_dir"
|
||||
python3 -m pip install --user -r requirements.txt || warn "Some dependencies may have failed to install"
|
||||
python3 -m pip install --user . || warn "MTKClient installation may be incomplete"
|
||||
cd - > /dev/null
|
||||
|
||||
log "MTKClient installed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
extract_boot_with_mtkclient() {
|
||||
print_header "Extracting Boot with MTKClient"
|
||||
|
||||
local mtk_dir="${WORK_DIR}/mtkclient"
|
||||
local boot_img="$WORK_DIR/boot.img"
|
||||
local boot_a_img="$WORK_DIR/boot_a.img"
|
||||
local vbmeta_a_img="$WORK_DIR/vbmeta_a.img"
|
||||
|
||||
if [[ ! -d $mtk_dir ]]; then
|
||||
error "MTKClient not installed. Run: $SCRIPT_NAME install-mtk"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " MTKClient Boot ROM Mode Instructions"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
echo "BEFORE continuing, you MUST:"
|
||||
echo
|
||||
echo " 1. Power off your phone COMPLETELY"
|
||||
echo " 2. DISCONNECT the USB cable from your phone"
|
||||
echo " 3. Have the USB cable ready in your hand"
|
||||
echo
|
||||
echo "When you press Enter:"
|
||||
echo
|
||||
echo " 4. Press and hold BOTH Volume buttons (Up + Down)"
|
||||
echo " 5. While holding BOTH buttons, connect USB cable"
|
||||
echo " 6. Keep holding until device is detected (may take 5-10 seconds)"
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
|
||||
if ! confirm "Phone is OFF and USB DISCONNECTED - ready to proceed?"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Starting MTKClient NOW - quickly enter BROM mode!"
|
||||
log "Hold BOTH volume buttons and connect USB cable..."
|
||||
echo
|
||||
|
||||
cd "$mtk_dir"
|
||||
|
||||
# Activate venv and extract boot and vbmeta
|
||||
if [[ ! -d "$mtk_dir/venv" ]]; then
|
||||
error "MTKClient virtual environment not found. Run: $SCRIPT_NAME install-mtk"
|
||||
cd - > /dev/null
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source venv/bin/activate
|
||||
# BL9000 uses A/B partitions, so extract boot_a and vbmeta_a
|
||||
if python3 mtk.py r boot_a,vbmeta_a "$boot_a_img,$vbmeta_a_img"; then
|
||||
log "Boot_a and vbmeta_a extracted successfully!"
|
||||
|
||||
# Copy boot_a.img to boot.img for Magisk compatibility
|
||||
cp "$boot_a_img" "$boot_img"
|
||||
BOOT_IMG="$boot_img"
|
||||
|
||||
log "Boot image ready for patching: $BOOT_IMG"
|
||||
|
||||
# Reset device
|
||||
log "Resetting device..."
|
||||
python3 mtk.py reset || warn "Failed to reset device, please reboot manually"
|
||||
|
||||
# Deactivate venv if function exists
|
||||
type deactivate &> /dev/null && deactivate
|
||||
|
||||
cd - > /dev/null
|
||||
return 0
|
||||
else
|
||||
# Deactivate venv if function exists
|
||||
type deactivate &> /dev/null && deactivate
|
||||
|
||||
error "Failed to extract boot image with MTKClient"
|
||||
cd - > /dev/null
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
extract_boot_image() {
|
||||
print_header "Extracting Boot Image"
|
||||
|
||||
local boot_img="$WORK_DIR/boot.img"
|
||||
|
||||
if [[ -n ${BOOT_IMG:-} && -f $BOOT_IMG ]]; then
|
||||
log "Using provided boot image: $BOOT_IMG"
|
||||
cp "$BOOT_IMG" "$boot_img"
|
||||
BOOT_IMG="$boot_img"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Attempting to extract boot image from device..."
|
||||
|
||||
# Method 1: Try MTKClient first (best for MediaTek devices)
|
||||
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
|
||||
log "Trying MTKClient extraction..."
|
||||
if extract_boot_with_mtkclient; then
|
||||
return 0
|
||||
fi
|
||||
warn "MTKClient extraction failed, trying ADB methods..."
|
||||
fi
|
||||
|
||||
# Method 2: Try to pull boot partition directly via ADB
|
||||
local boot_partition
|
||||
boot_partition=$(adb shell "find /dev/block -name boot | head -n1" 2> /dev/null | tr -d '\r\n' || echo "")
|
||||
|
||||
if [[ -n $boot_partition ]]; then
|
||||
log "Found boot partition: $boot_partition"
|
||||
if adb pull "$boot_partition" "$boot_img" 2> /dev/null; then
|
||||
log "Boot image extracted successfully"
|
||||
BOOT_IMG="$boot_img"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 3: Try to get boot partition via by-name
|
||||
boot_partition=$(adb shell "ls /dev/block/by-name/boot*" 2> /dev/null | head -n1 | tr -d '\r\n' || echo "")
|
||||
|
||||
if [[ -n $boot_partition ]]; then
|
||||
log "Found boot partition: $boot_partition"
|
||||
if adb shell "su -c 'dd if=$boot_partition of=/sdcard/boot.img'" 2> /dev/null &&
|
||||
adb pull /sdcard/boot.img "$boot_img" 2> /dev/null; then
|
||||
adb shell rm /sdcard/boot.img 2> /dev/null || true
|
||||
log "Boot image extracted successfully"
|
||||
BOOT_IMG="$boot_img"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
error "Failed to extract boot image automatically."
|
||||
echo
|
||||
echo "Manual extraction options:"
|
||||
echo "1. Use MTKClient: $SCRIPT_NAME extract-mtk"
|
||||
echo "2. Extract boot.img from your device's firmware package"
|
||||
echo "3. Get boot.img from device manufacturer's official ROM"
|
||||
echo
|
||||
echo "Then run: $SCRIPT_NAME root --boot-img /path/to/boot.img"
|
||||
echo
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
patch_boot_with_magisk() {
|
||||
print_header "Patching Boot Image with Magisk"
|
||||
|
||||
if [[ ! -f ${BOOT_IMG:-} ]]; then
|
||||
die "Boot image not found: ${BOOT_IMG:-none}"
|
||||
fi
|
||||
|
||||
local magisk_apk="$WORK_DIR/magisk.apk"
|
||||
if [[ ! -f $magisk_apk ]]; then
|
||||
die "Magisk APK not found. Run download step first."
|
||||
fi
|
||||
|
||||
log "Checking if device is connected..."
|
||||
if ! adb devices | grep -q "device$"; then
|
||||
die "No device detected. Make sure USB debugging is enabled and device is connected."
|
||||
fi
|
||||
|
||||
log "Installing Magisk APK on device..."
|
||||
if ! adb install -r "$magisk_apk" 2> /dev/null; then
|
||||
warn "Magisk APK installation failed (may already be installed)"
|
||||
fi
|
||||
|
||||
log "Pushing boot image to device..."
|
||||
adb push "$BOOT_IMG" /sdcard/Download/boot.img || die "Failed to push boot image"
|
||||
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " MANUAL STEP REQUIRED"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
echo "On your phone:"
|
||||
echo "1. Open the Magisk app"
|
||||
echo "2. Tap 'Install' next to Magisk"
|
||||
echo "3. Select 'Select and Patch a File'"
|
||||
echo "4. Navigate to Downloads and select boot.img"
|
||||
echo "5. Tap 'Let's Go' and wait for patching to complete"
|
||||
echo "6. The patched file will be saved as magisk_patched_*.img"
|
||||
echo
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo
|
||||
|
||||
if ! confirm "Have you completed patching the boot image in Magisk app?"; then
|
||||
error "Patching cancelled by user"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Pulling patched boot image from device..."
|
||||
|
||||
local patched_img
|
||||
patched_img=$(adb shell "ls /sdcard/Download/magisk_patched_*.img 2>/dev/null" | tr -d '\r\n' | head -n1 || echo "")
|
||||
|
||||
if [[ -z $patched_img ]]; then
|
||||
error "Could not find patched boot image on device."
|
||||
echo "Please ensure the patching completed successfully in Magisk app."
|
||||
return 1
|
||||
fi
|
||||
|
||||
PATCHED_BOOT_IMG="$WORK_DIR/magisk_patched.img"
|
||||
if ! adb pull "$patched_img" "$PATCHED_BOOT_IMG"; then
|
||||
error "Failed to pull patched boot image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Patched boot image saved to: $PATCHED_BOOT_IMG"
|
||||
return 0
|
||||
}
|
||||
|
||||
flash_patched_boot() {
|
||||
print_header "Flashing Patched Boot Image"
|
||||
|
||||
if [[ ! -f ${PATCHED_BOOT_IMG:-} ]]; then
|
||||
die "Patched boot image not found: ${PATCHED_BOOT_IMG:-none}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${YELLOW}This will flash the patched boot image to your device.${NC}"
|
||||
echo "Device uses A/B partitions - will flash to boot_a"
|
||||
echo
|
||||
|
||||
if ! confirm "Proceed with flashing?"; then
|
||||
log "Flashing cancelled by user"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Rebooting to bootloader..."
|
||||
adb reboot bootloader || die "Failed to reboot to bootloader"
|
||||
|
||||
log "Waiting for fastboot mode..."
|
||||
sleep 5
|
||||
|
||||
if ! sudo fastboot devices | grep -q .; then
|
||||
die "Device not detected in fastboot mode"
|
||||
fi
|
||||
|
||||
log "Flashing patched boot image to boot_a..."
|
||||
if ! sudo fastboot flash boot_a "$PATCHED_BOOT_IMG"; then
|
||||
error "Failed to flash boot image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Flashed successfully!"
|
||||
log "Rebooting device..."
|
||||
sudo fastboot reboot
|
||||
|
||||
log "Waiting for device to boot..."
|
||||
sleep 10
|
||||
adb wait-for-device || true
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ Root Process Complete! ║${NC}"
|
||||
echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}"
|
||||
echo
|
||||
echo "Your BL9000 phone should now be rooted with Magisk!"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo "1. Open the Magisk app on your phone"
|
||||
echo "2. Verify that it shows 'Installed' for both Magisk and App"
|
||||
echo "3. Grant root access to apps as needed"
|
||||
echo "4. Install Magisk modules if desired"
|
||||
echo
|
||||
echo "Note: Some banking and secure apps may not work with root."
|
||||
echo " Use Magisk's DenyList feature to hide root from specific apps."
|
||||
echo
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
clean_work_dir() {
|
||||
if [[ -d $WORK_DIR ]]; then
|
||||
log "Removing working directory: $WORK_DIR"
|
||||
rm -rf "$WORK_DIR"
|
||||
log "Cleaned successfully"
|
||||
else
|
||||
log "Work directory doesn't exist: $WORK_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
run_full_process() {
|
||||
print_header "BL9000 Full Root Process"
|
||||
|
||||
log "Starting complete rooting process..."
|
||||
|
||||
install_dependencies || die "Failed to install dependencies"
|
||||
setup_udev_rules || true
|
||||
|
||||
echo
|
||||
if ! confirm "Continue to device check?"; then
|
||||
die "Process cancelled by user"
|
||||
fi
|
||||
|
||||
check_device || die "Device check failed"
|
||||
|
||||
echo
|
||||
if ! confirm "Continue to backup device data?"; then
|
||||
die "Process cancelled by user"
|
||||
fi
|
||||
|
||||
backup_device_data || warn "Backup failed or incomplete"
|
||||
|
||||
echo
|
||||
if ! confirm "Continue to bootloader unlock?"; then
|
||||
die "Process cancelled by user"
|
||||
fi
|
||||
|
||||
unlock_bootloader || die "Bootloader unlock failed"
|
||||
|
||||
echo
|
||||
log "Please complete device setup and re-enable USB debugging, then press Enter..."
|
||||
read -r
|
||||
|
||||
check_device || die "Device check failed after unlock"
|
||||
|
||||
download_magisk || die "Failed to download Magisk"
|
||||
extract_boot_image || die "Failed to extract boot image"
|
||||
patch_boot_with_magisk || die "Failed to patch boot image"
|
||||
flash_patched_boot || die "Failed to flash patched boot"
|
||||
|
||||
log "Full root process completed successfully!"
|
||||
}
|
||||
|
||||
run_root_only() {
|
||||
print_header "BL9000 Root Process (Skip Unlock)"
|
||||
|
||||
log "Starting root process (assuming bootloader is already unlocked)..."
|
||||
|
||||
check_device || die "Device check failed"
|
||||
download_magisk || die "Failed to download Magisk"
|
||||
extract_boot_image || die "Failed to extract boot image"
|
||||
patch_boot_with_magisk || die "Failed to patch boot image"
|
||||
flash_patched_boot || die "Failed to flash patched boot"
|
||||
|
||||
log "Root process completed successfully!"
|
||||
}
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
# shellcheck source=lib/bl9000_usage.sh
|
||||
source "$SCRIPT_DIR/lib/bl9000_usage.sh"
|
||||
# shellcheck source=lib/bl9000_deps.sh
|
||||
source "$SCRIPT_DIR/lib/bl9000_deps.sh"
|
||||
# shellcheck source=lib/bl9000_backup.sh
|
||||
source "$SCRIPT_DIR/lib/bl9000_backup.sh"
|
||||
# shellcheck source=lib/bl9000_device.sh
|
||||
source "$SCRIPT_DIR/lib/bl9000_device.sh"
|
||||
# shellcheck source=lib/bl9000_mtkclient.sh
|
||||
source "$SCRIPT_DIR/lib/bl9000_mtkclient.sh"
|
||||
# shellcheck source=lib/bl9000_magisk.sh
|
||||
source "$SCRIPT_DIR/lib/bl9000_magisk.sh"
|
||||
|
||||
main() {
|
||||
require_non_root
|
||||
|
||||
Loading…
Reference in New Issue
Block a user