ippo

Termux bash scripts

Termux bash scripts

This post aims to maintain a growing list of bash scripts that can be run on the andorid terminal to perform tasks not available through the User Interface or an application. Some may work over adb with adb shell Tested them with root directly on termux.

Search for GitHub repos that match a search query and contain apk releases.

  • Parse search query as argument.
  • Look for matches in repo names, descriptions, and read.me files.
  • Perform search in repos started from the more recent ones.
  • Return repos that contain apk releases.
  • Github api limits query matches to 1000 so a max of 1000 matched repos will be checked for apk files.
  • Usage: save the script to file and run python script.py “search_query” requires python obviously and a Github token (the limit without a token is 100 queries per hour)
  • Requires Python pkg install python and requests pip install requests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import requests
import argparse
import base64
from datetime import datetime, timedelta

# Hardcoded GitHub personal access token
GITHUB_TOKEN = "your_personal_access_token"  # Replace with your actual token

def search_repos_and_check_apk(search_query):
    headers = {
        "Authorization": f"token {GITHUB_TOKEN}",
        "Accept": "application/vnd.github.v3+json"
    }

    # Get the date 1 year ago from today
    one_year_ago = datetime.now() - timedelta(days=365)
    
    # Set the base search URL and parameters
    search_url = "https://api.github.com/search/repositories"
    per_page = 100  # Set to the maximum number of repositories per page

    total_repos_checked = 0
    total_repos_with_apk = 0
    matching_repos = {}
    page = 1
    max_repos = 1000  # Limit to GitHub's maximum search results

    while total_repos_checked < max_repos:
        # Search for repositories with the query in their description
        search_params = {
            "q": f"{search_query} in:description",
            "sort": "updated",
            "order": "desc",
            "per_page": per_page,
            "page": page
        }

        response = requests.get(search_url, headers=headers, params=search_params)
        
        if response.status_code != 200:
            print(f"Error fetching repositories: {response.status_code}")
            break

        repositories = response.json().get("items", [])

        if not repositories:
            print("No more repositories found.")
            break

        # Check for README.md and APK releases in each repository
        for repo in repositories:
            total_repos_checked += 1

            repo_full_name = repo["full_name"]
            repo_updated_at = datetime.strptime(repo["updated_at"], "%Y-%m-%dT%H:%M:%SZ")
            repo_archived = repo["archived"]

            # Exclude repositories that are archived or not updated within the last year
            if repo_archived or repo_updated_at < one_year_ago:
                continue

            # Fetch repository contents to find README.md
            contents_url = f"https://api.github.com/repos/{repo_full_name}/contents"
            contents_response = requests.get(contents_url, headers=headers)
            
            if contents_response.status_code == 200:
                contents = contents_response.json()
                readme_content = None

                # Look for README.md in repository contents
                for item in contents:
                    if item["name"].lower() == "readme.md" and item["type"] == "file":
                        readme_url = item["url"]
                        readme_response = requests.get(readme_url, headers=headers)
                        
                        if readme_response.status_code == 200:
                            readme_data = readme_response.json()
                            readme_content = base64.b64decode(readme_data["content"]).decode("utf-8")
                            if search_query.lower() in readme_content.lower():
                                break

            # Check for APK releases in each repository
            releases_url = f"https://api.github.com/repos/{repo_full_name}/releases"
            releases_response = requests.get(releases_url, headers=headers)
            if releases_response.status_code == 200:
                releases = releases_response.json()
                
                if releases:
                    for release in releases:
                        for asset in release.get("assets", []):
                            if asset["name"].endswith(".apk"):
                                if repo_full_name not in matching_repos or release["published_at"] > matching_repos[repo_full_name]["release_date"]:
                                    matching_repos[repo_full_name] = {
                                        "repo_name": repo["name"],
                                        "repo_url": repo["html_url"],
                                        "description": repo["description"],
                                        "release_tag": release["tag_name"],
                                        "release_date": release["published_at"],
                                        "apk_name": asset["name"],
                                        "apk_download_url": asset["browser_download_url"]
                                    }
                                    total_repos_with_apk += 1

        # Move to the next page
        page += 1
        if len(repositories) < per_page:
            # If the number of repositories fetched is less than per_page, there are no more pages
            break

    # Display results
    if not matching_repos:
        print("No repositories with APK releases found.")
    else:
        # Sort matching repos by release date
        sorted_repos = sorted(matching_repos.values(), key=lambda x: x["release_date"], reverse=True)
        
        for repo in sorted_repos:
            print(f"Repo: {repo['repo_name']} ({repo['repo_url']})")
            print(f"Description: {repo['description']}")
            print(f"APK: {repo['apk_name']} - Released on: {repo['release_date']}")
            print(f"Download URL: {repo['apk_download_url']}")
            print("====================================")
    
    # Print totals
    print(f"Total repositories checked: {total_repos_checked}")
    print(f"Total repositories with APK releases: {total_repos_with_apk}")

if name == "main":
    # Setup command line argument parsing
    parser = argparse.ArgumentParser(description="Search GitHub repositories for APK releases.")
    parser.add_argument("search_query", type=str, help="The search query for the repository description and README.md.")

    args = parser.parse_args()

    # Call the search function with the command-line argument
    search_repos_and_check_apk(search_query=args.search_query)

Save scipt and bash script.sh > list.txt if you want the list saved to a file

My install sources are: “Aurora/Play Store Apps” “com.android.vending” “Aurora Droid Apps” “com.aurora.adroid” “Obtainium Apps” “dev.imranr.obtainium” “APK Installs” “com.android.packageinstaller” “Unknown” “null”

refine accordingly to match yours

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/bash

# Define a temporary file to hold the raw data
temp_file=$(mktemp)

# Extract APK file paths, package names, and installation methods
pm list packages -3 -f -i | sed -n 's|package:\(/data/app/[^/]\+/[^/]\+/base.apk\)=\([^ ]\+\)  installer=\([^ ]\+\)|\1 \2 \3|p' | while read apk package installer; do
    # Extract application name using aapt
    app_name=$(aapt dump badging "$apk" 2>/dev/null | grep 'application: label=' | awk -F"'" '{print $2}')
    
    # Format application name based on word count
    if [ $(echo "$app_name" | awk '{print NF}') -le 1 ]; then
        short_name="$app_name"
    else
        short_name=$(echo "$app_name" | awk '{print $1 "_" $2}')
    fi
    
    # Print application name, package name, and installer to the temporary file
    echo "$short_name $package $installer" >> "$temp_file"
done

# Define function to print section with separator
print_section() {
    local section_title="$1"
    local installer_filter="$2"
    
    echo "===================================="
    echo "$section_title:"
    echo "------------------------------------"
    grep "$installer_filter" "$temp_file" | awk '{print $1, $2}'
    echo
}

# Print sections with visual separators
print_section "Aurora/Play Store Apps (com.android.vending)" 'com.android.vending'
print_section "Aurora Droid Apps (com.aurora.adroid)" 'com.aurora.adroid'
print_section "Obtainium Apps (dev.imranr.obtainium)" 'dev.imranr.obtainium'
print_section "APK Installs (com.android.packageinstaller)" 'com.android.packageinstaller'

# Print unknown category with proper handling
echo "===================================="
echo "Unknown (null):"
echo "------------------------------------"
grep -v 'com.android.vending\|com.aurora.adroid\|dev.imranr.obtainium\|com.android.packageinstaller' "$temp_file" | awk '{print $1, $2}'

# Clean up temporary file
rm "$temp_file"

List all packages that grants a specific permission e.g. for signature spoofing

1
2
3
4
5
6
for package in $(su -c 'pm list packages' | cut -d':' -f2); do
    result=$(su -c "dumpsys package $package 2>/dev/null | grep FAKE_PACKAGE_SIGNATURE")
    if [[ $result =~ FAKE_PACKAGE_SIGNATURE ]]; then
        echo "$package: $(echo $result | awk -F'android.permission.FAKE_PACKAGE_SIGNATURE:' '{print $2}')"
    fi
done

doze

Test if doze is enabled

su -c dumpsys deviceidle enabled

Should return 1

Enable doze

su -c dumpsys deviceidle enable

su -c dumpsys deviceidle force-idle

since Android 12, doze flags have been migrated to DeviceConfig with device_idle as the namespace. To change the configuration, use adb shell device_config put device_idle [KEY] [VALUE]

e.g. device_config put device_idle sensing_to 0

Some recomended values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash

device_config reset trusted_defaults device_idle
device_config put device_idle light_after_inactive_to 30000
device_config put device_idle light_pre_idle_to 120000
device_config put device_idle light_idle_to 300000
device_config put device_idle light_idle_factor 2
device_config put device_idle light_max_idle_to 900000
device_config put device_idle light_idle_maintenance_min_budget 30000
device_config put device_idle light_idle_maintenance_max_budget 180000
device_config put device_idle inactive_to 900000
device_config put device_idle sensing_to 0
device_config put device_idle locating_to 0
device_config put device_idle motion_inactive_to 0
device_config put device_idle idle_after_inactive_to 900000
device_config put device_idle idle_pending_to 60000
device_config put device_idle max_idle_pending_to 120000
device_config put device_idle idle_pending_factor 2
device_config put device_idle idle_to 900000
device_config put device_idle max_idle_to 21600000
device_config put device_idle idle_factor 2
device_config put device_idle wait_for_unlock true

Make changes persistent

su -c device_config set_sync_disabled_for_tests persistent

Read: https://developer.android.com/training/monitoring-device-state/doze-standby

restrict backround battery abusive apps

su -c device_config put activity_manager bg_auto_restrict_abusive_apps 1

su -c device_config put activity_manager bg_current_drain_auto_restrict_abusive_apps_enabled 1

Read:https://source.android.com/docs/core/power/trackers

batterystats

To get a detailed power estimate

su -c 'dumpsys batterystats' | awk '/Statistics since last charge:/,/idle/'

Get screen off discharge rate. Based on Time on battery screen off: and Screen off discharge: sections from dumpsys batterystats

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

result1=$(su -c 'dumpsys batterystats | grep "Time on battery screen off" | awk -F"[hms ]+" "/Time on battery screen off:/ {print \$8*60 + \$9 + \$10/60}"')
result2=$(su -c 'dumpsys batterystats | grep "Screen off discharge" | awk "/Screen off discharge:/ {print \$4}"')

# Checking if both results are non-empty before division
if [[ -n $result1 && -n $result2 ]]; then
    result3=$(awk -v r2="$result2" -v r1="$result1" 'BEGIN {printf "%.2f", r2 * 60 / r1}')
    result=$(awk -v r3="$result3" 'BEGIN {printf "%.2f", r3 * 100 / 5000}')
    echo $result
else
    echo "One or both of the results are empty."
fi

Get screen on discharge rate. Based on “Screen on:” and “Screen on discharge:” sections from dumpsys batterystats

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

result1=$(su -c 'dumpsys batterystats | grep "Screen on:" | awk -F"[hms ]+" "/Screen on:/ {print \$4*60 + \$5 + \$6/60; exit}"')
result2=$(su -c 'dumpsys batterystats | grep "Screen on discharge:" | awk "/Screen on discharge:/ {print \$4}"')

# Checking if both results are non-empty before division
if [[ -n $result1 && -n $result2 ]]; then
    result3=$(awk -v r2="$result2" -v r1="$result1" 'BEGIN {printf "%.2f", r2 * 60 / r1}')
    result=$(awk -v r3="$result3" 'BEGIN {printf "%.2f", r3 * 100 / 5000}')
    echo $result
else
    echo "One or both of the results are empty."
fi

disable wifi multicast

Some router have wifi multicast enabled and no ui to disable it. It can lead to kernel wakelocks and battery drain.

Check battery drain by WiFi interface

su -c dumpsys wifi |grep mAh

Monitor the interface for multicast

su -c ifconfig wlan0

Disable it

su -c ifconfig wlan0 -multicast

Enable it (just saying)

su -c ifconfig wlan0 multicast

Multicast parameters filepath

su -c cat /proc/net/igmp |grep wlan

Check if it’s enabled in the compressed Kernel configuration under /proc/config.gz

su -c zcat /proc/config.gz |grep CONFIG_IP_MULTICAST

Disabled does not preserve reboot.

Run a script on boot .

With termux-boot:

  • Download, unpackage and install from GitHub actions artifacts https://github.com/termux/termux-boot/actions

  • Add su -c ifconfig wlan0 -multicast in a .sh file under ~/.termux/boot/

ifconfig is deprecated you could use ip:

pkg install iproute2

su -c ip link show

Reveals more interfaces using multicast:

su -c 'ip link show | grep MULTICAST'

  • wifi-aware0 (nearby devices)
  • p2p0 (WiFi direct)
  • gretap (tunel interface)
  • erspan (wtf is this?)

bugreport

Open a root terminal and type su -c bugreportz device will vibrate and it will output a zip file under/data/user_de/0/com.android.shell/files/bugreports Upload and analize it at https://bathist.dev

Read:https://developer.android.com/topic/performance/power/setup-battery-historian

List permissions of an apk file

aapt d permissions /filepath/file.apk

comments powered by Disqus