В связи с тем, что мобильные устройства уже давно имеют обширный функционал, то задачи автоматизации можно смело переносить и на них. И, как нельзя лучше, здесь так же хорошо подходит cron для их выполнения. Но если в «обычных» Linux системах настройка cron занимает мало времени, то Android устройство требует более сложной работы по его настройке.
Если тебе интересна тема автоматизации и ты хочешь, чтобы твои shell-скрипты запускались сразу же после загрузки устройства, да еще и могли бы запускаться по таймеру — добро пожаловать под кат!
Предисловие
Я занимаюсь автоматизацией мобильных устройств под Android. И во время выполнения автоматических скриптов происходит множество непредвиденных ситуаций, даже если для тестирования используются одинаковые устройства.
Самые популярные проблемы:
0. Скрипт автоматизации выполняет не то, что ты хотел
1. Мобильное приложение автоматически выгружается
2. Автоматическая перезагрузка телефона
3. Мобильное приложение автоматически не запускается после перезапуска
4. Wi-Fi модуль произвольно отключается, не находит сеть, не подключается к сети
5. Мобильная сеть неожиданно пропала
6. Телефон ушел в спящий режим
7. Отпал прокси или сам сервер или сервер вернул странный ответ
Из-за этого приходится постоянно следить за устройством и отлавливать эти непредвиденные ситуации.
Таким образом, я пришел к тому, что cron с «правильными» скриптами позволит отследить программные сбои и восстановить скрипт автоматизации или запустить его заново. Но как оказалось, хотя Android содержит ядро Linux, но есть особые нюансы, с которыми пришлось разбираться. Итак, давайте приступим к настройке!
Настройка Cron
Настраиваем окружение
- Устанавливаем adb для доступа к устройству через shell используя usb-провод.
- Открываем раздел Для разработчиков. Для этого заходим в раздел О телефоне и делаем несколько нажатий на Номер сборки или на что-то похожее.
- Заходим в раздел Для разработчиков и включаем его. Подключаем устройство к компьютеру и разрешаем доступ на этом компьютере к данному устройству.
- Добавляем root для вашего устройства. Самые распространенные варианты — это SuperSu, Magisk и Kingroot. Зайдите на 4pda и найдите вариант root'а под ваше устройство. К сожалению, универсального root'а не существует.
- Устанавливаем BusyBox (так же есть на 4pda, например), так как он как раз содержит в себе cron-программу.
Настройка ручного запуска
- Подключаемся к телефону с помощью adb shell (если adb у вас не прописана в переменной среды окружения, то добавьте полный путь.
- Переходим в режим root с помощью команды su
- Проверяем наличие cron-программы и посмотрим настройки с помощью команды crond -h
результат выполнения
crond: invalid option -- h
BusyBox v1.29.2-Stericson (2018-08-12 11:19:12 EDT) multi-call binary.
Usage: crond -fbS -l N -d N -L LOGFILE -c DIR
-f Foreground
-b Background (default)
-S Log to syslog (default)
-l N Set log level. Most verbose 0, default 8
-d N Set log level, log to stderr
-L FILE Log to FILE
-c DIR Cron dir. Default:/var/spool/cron/crontabs
Как видно из последней строки, инструкции по умолчанию должны храниться в директории /var/spool/cron/crontabs, которая автоматически не создается и если мы запустим команду
crond -b
и потом проверим запустился ли процесс через ps | grep crond
, то его там может и не быть, т.к. он не смог получить какие-либо инструкции. Поэтому давайте выполним команду crond -b -fd0
и посмотрим в чем причина. Скорее всего у вас будет подобная ошибка:crond: can't change directory to '/var/spool/cron/crontabs': No such file or directory
. В данном случае это нормально, т.к. в будущем мы сами укажем путь до исолняемого crontab файла.4. Создадим простой crontab файл:
mkdir /data/crontab
echo "*/1 * * * * echo 'text' >> /sdcard/test.txt" > /data/crontab/root
Теперь у нас есть задание, которое каждую минуту будет добавлять слово text в файл /sdcard/test.txt
Запускаем:
crond -b -fd0 -c /data/crontab
и получаем следующий лог:crond: crond (busybox 1.29.2-Stericson) started, log level 0
crond: ignoring file 'root' (no such user)
...
Конечно, немного удивляет, ведь если мы выполним команду whoami то в результате она вернёт root.
5. Добавим пользователя root, раз crond просит:
mount -o remount,rw /system;
echo "root:x:0:0::/system/etc/crontabs:/system/bin/sh" >> /system/etc/passwd;
mount -o remount,rw /system;
Из-за отсутствия данного файла, я понял, что в Android системе он совсем не задействован. Если вы уверены в том, где вы будете хранить свои crontab файлы, то вы можете заменить строку /system/etc/crontabs на нужную вам. Снова выполняем команду
crond -b -fd0 -c /data/crontab
И получаем следующее:
crond: user:root entry:*/1 * * * * echo 'text' >> /sdcard/test.txt
111111111111111111111111111111111111111111111111111111111111
111111111111111111111111
11111111111111111111111111111111
111111111111
1111111
crond: wakeup dt=16
crond: file root:
crond: line echo 'text' >> /sdcard/test.txt
crond: job: 0 echo 'text' >> /sdcard/test.txt
crond: can't change directory to '/system/etc/crontabs'
crond: can't change directory to '/var/spool/cron': No such file or directory
crond: USER root pid 12849 cmd echo 'text' >> /sdcard/test.txt
Хотя, если верить логу задача в crond прописалась, но в моем случае файл не создался. Решить проблему можно очень просто:
mkdir -p /system/etc/crontabs
Ну хочет он, чтобы существовала там директория, кто мы такие, чтобы ему запрещать! Запускаем снова и видим:
crond: user:root entry:*/1 * * * * echo 'text' >> /sdcard/test.txt
111111111111111111111111111111111111111111111111111111111111
111111111111111111111111
11111111111111111111111111111111
111111111111
1111111
crond: wakeup dt=12
crond: file root:
crond: line echo 'text' >> /sdcard/test.txt
crond: job: 0 echo 'text' >> /sdcard/test.txt
crond: child running /system/bin/sh
crond: USER root pid 13033 cmd echo 'text' >> /sdcard/test.txt
Ошибки ушли, и появилась строка crond: child running /system/bin/sh. Наконец-то cron у нас успешно завелся, и можно переходить ко второй части!
Автоматическая загрузка shell-скрипта
В Linux системе есть директория init.d, которая отвечает за автозапуск сразу же после загрузки системы, поэтому попробуем идти по этому пути!
1. Проверяем, существует ли данная директория у вас на устройстве (это /etc/init.d либо /system/etc/init.d — это тот же смонтированный раздел etc ). В моем случае её нет. Ну что, тогда создаем:
mount -o remount,rw /system
mkdir /system/etc/init.d
chmod 0755 /system/etc/init.d
mount -o remount,rw /system
Теперь добавим туда какой-нибудь простенький скрипт, например:
echo "echo 'Init.d is working !!!' >> /sdcard/init_test.log" > /system/etc/init.d/90my_script
chmod 777 /system/etc/init.d/90my_script
Перезагружаем устройство и смотрим, случилось ли чудо… К сожалению, у меня файл не появился.
Исследуем систему дальше и ищем какой-нибудь init файл, который может запускать скрипты после запуска. У меня на устройстве оказался файл в /init.rc. Ну что, попробуем его изменить и перезагрузим устройство:
mount -o remount,rw /
echo "echo 'Init.d is working !!!' >> /sdcard/init_test.log" >> /init.rc
mount -o remount,rw /
reboot
Но файл опять не создался. Идем смотреть на файл /init.rc и наша запись пропала и файл как бы и не менялся, т.к. дата создания стоит совсем какая-то странная (в моем случае 01 янв. 70 05:00:00).
Продолжаем разбираться, и оказывается что данный файл храниться в boot.img, и каждый раз достается из него. И для того, чтобы изменить функционал файла init.rc нужно выполнить все это.
Но есть более простой способ, который поможет решить данную задачу. Для этого способа мы можем использовать следующий shell-скрипт (скажем спасибо Ryuinferno):
Shell-скрипт
#!/system/bin/sh
#Script to enable init.d by Ryuinferno @ XDA
error_msg(){
echo "You do not need this mod..."
sleep 1
echo "If you are reapplying, please delete these files if present:"
echo "/system/bin/sysinit"
sleep 1
echo "/system/etc/install-recovery.sh"
sleep 1
echo "/system/etc/install-recovery-2.sh"
sleep 1
echo "And run again..."
sleep 1
echo "If init.d is still not working, read the FAQ part in my thread..."
sleep 1
echo "Aborting..."
mount -o remount,ro -t auto /system
echo ""
echo "Ryuinferno @ XDA"
exit 1
}
echo "Init.d Enabler by Ryuinferno @ XDA"
echo ""
sleep 1
id=`id`;
id=`echo ${id#*=}`;
id=`echo ${id%%\(*}`;
id=`echo ${id%% *}`
if [ "$id" != "0" ] && [ "$id" != "root" ]; then
echo "Script NOT running as root!"
sleep 1
echo "Superuser access not granted!"
sleep 1
echo "Please type 'su' first before running this script..."
exit 1
else
echo "Hello Supaa User! :P"
echo ""
sleep 1
fi
if [ ! "'which busybox'" ]; then
echo "busybox NOT INSTALLED!"
sleep 1
echo "Please install busybox first!"
exit 1
else
echo "busybox found!"
sleep 1
fi
bbb=0
if [ ! "`which grep`" ]; then
bbb=1
echo "grep applet NOT FOUND!"
sleep 1
else
echo "Awesome! grep found! :D"
sleep 1
fi
if [ ! "`which run-parts`" ]; then
bbb=1
echo "run-parts applet NOT FOUND!"
sleep 1
else
echo "Good! run-parts found! :)"
echo ""
sleep 1
fi
if [ $bbb -eq 1 ] ; then
echo ""
echo "Required applets are NOT FOUND!"
echo ""
sleep 1
echo "Please reinstall busybox!"
exit 1
fi
echo "Great! Let's proceed..."
echo ""
sleep 1
echo "Press enter to continue..."
read enterKey
clear
sleep 1
echo "Mounting system as rewritable..."
mount -o remount,rw -t auto /system
sleep 1
echo "Removing old sysinit file"
rm /system/bin/sysinit
sleep 1
echo ""
echo "Checking for the presence of sysinit in /system/bin..."
sleep 1
if [ -e /system/bin/sysinit ]; then
echo "sysinit found..."
if [ -z "`cat /system/bin/sysinit | grep "init.d"`" ]; then
echo "Adding lines to sysinit..."
echo "" >> /system/bin/sysinit
echo "# init.d support" >> /system/bin/sysinit
echo "" >> /system/bin/sysinit
echo "export PATH=/sbin:/system/sbin:/system/bin:/system/xbin" >> /system/bin/sysinit
echo "run-parts /system/etc/init.d" >> /system/bin/sysinit
echo "" >> /system/bin/sysinit
else
echo ""
echo "Your sysinit should already be running the scripts in init.d folder at boot..."
error_msg
fi
else
echo "sysinit not found, creating file..."
echo "#!/system/bin/sh" > /system/bin/sysinit
echo "# init.d support" >> /system/bin/sysinit
echo "" >> /system/bin/sysinit
echo "export PATH=/sbin:/system/sbin:/system/bin:/system/xbin" >> /system/bin/sysinit
echo "run-parts /system/etc/init.d" >> /system/bin/sysinit
echo "" >> /system/bin/sysinit
fi
sleep 1
echo "Setting correct permissions and ownership for sysinit..."
chmod 755 /system/bin/sysinit
chown 0.2000 /system/bin/sysinit
sleep 1
echo ""
echo "Checking for the presence of install-recovery.sh..."
sleep 1
if [ -f /system/etc/install-recovery.sh ] && [ -z "`cat /system/etc/install-recovery.sh | grep "daemon"`" ]; then
if [ ! -z "`cat /system/etc/install-recovery.sh | grep "init.d"`" ];then
echo "Your install-recovery.sh seems to be already modified for init.d..."
error_msg
fi
echo "install-recovery.sh found, renaming it as install-recovery-2.sh..."
mv /system/etc/install-recovery.sh /system/etc/install-recovery-2.sh
echo "Recreating install-recovery.sh..."
echo "#!/system/bin/sh" > /system/etc/install-recovery.sh
echo "# init.d support" >> /system/etc/install-recovery.sh
echo "" >> /system/etc/install-recovery.sh
echo "/system/bin/sysinit" >> /system/etc/install-recovery.sh
echo "" >> /system/etc/install-recovery.sh
echo "# excecuting extra commands" >> /system/etc/install-recovery.sh
echo "/system/etc/install-recovery-2.sh" >> /system/etc/install-recovery.sh
echo "" >> /system/etc/install-recovery.sh
elif [ -f /system/etc/install-recovery.sh ] && [ ! -z "`cat /system/etc/install-recovery.sh | grep "daemon"`" ]; then
if [ -f /system/etc/install-recovery-2.sh ] && [ ! -z "`cat /system/etc/install-recovery-2.sh | grep "init.d"`" ];then
echo "Your install-recovery-2.sh seems to be already modified for init.d..."
error_msg
fi
echo "install-recovery.sh is used for superuser, using install-recovery-2.sh instead..."
if [ -f /system/etc/install-recovery-2.sh ]; then
echo "" >> /system/etc/install-recovery-2.sh
echo "# init.d support" >> /system/etc/install-recovery-2.sh
echo "/system/bin/sysinit" >> /system/etc/install-recovery-2.sh
echo "" >> /system/etc/install-recovery-2.sh
else
echo "#!/system/bin/sh" > /system/etc/install-recovery-2.sh
echo "# init.d support" >> /system/etc/install-recovery-2.sh
echo "" >> /system/etc/install-recovery-2.sh
echo "/system/bin/sysinit" >> /system/etc/install-recovery-2.sh
echo "" >> /system/etc/install-recovery-2.sh
fi
if [ -z "`cat /system/etc/install-recovery.sh | grep "install-recovery-2.sh"`" ]; then
echo "" >> /system/etc/install-recovery.sh
echo "# extra commands" >> /system/etc/install-recovery.sh
echo "/system/etc/install-recovery-2.sh" >> /system/etc/install-recovery.sh
echo "" >> /system/etc/install-recovery.sh
fi
else
echo "install-recovery.sh not found, creating it..."
echo "#!/system/bin/sh" > /system/etc/install-recovery.sh
echo "# init.d support" >> /system/etc/install-recovery.sh
echo "" >> /system/etc/install-recovery.sh
echo "/system/bin/sysinit" >> /system/etc/install-recovery.sh
echo "" >> /system/etc/install-recovery.sh
fi
sleep 1
echo "Setting the correct permissions and ownership for install-recovery.sh..."
echo "Also for install-recovery-2.sh if it exists..."
chmod 755 /system/etc/install-recovery.sh
chown 0.0 /system/etc/install-recovery.sh
if [ -f /system/etc/install-recovery-2.sh ]; then
chmod 755 /system/etc/install-recovery-2.sh
chown 0.0 /system/etc/install-recovery-2.sh
fi
sleep 1
echo ""
echo "Checking for the presence of the init.d folder..."
sleep 1
if [ -d /system/etc/init.d ]; then
echo "init.d folder found..."
else
echo "init.d folder not found, creating the folder..."
mkdir /system/etc/init.d
fi
sleep 1
echo ""
echo "Creating basic init.d scripts..."
echo "#!/system/bin/sh" > /system/etc/init.d/08setperm
echo "#set correct permissions to /system/etc/init.d folder" >> /system/etc/init.d/08setperm
echo "" >> /system/etc/init.d/08setperm
echo "mount -o remount,rw -t auto /system" >> /system/etc/init.d/08setperm
echo "chmod -R 777 /system/etc/init.d" >> /system/etc/init.d/08setperm
echo "mount -o remount,ro -t auto /system" >> /system/etc/init.d/08setperm
echo "" >> /system/etc/init.d/08setperm
echo "#!/system/bin/sh" > /system/etc/init.d/00test
echo "#init.d test" >> /system/etc/init.d/00test
echo "" >> /system/etc/init.d/00test
echo "if [ -f /data/Test.log ]; then" >> /system/etc/init.d/00test
echo "rm /data/Test.log" >> /system/etc/init.d/00test
echo "fi" >> /system/etc/init.d/00test
echo "" >> /system/etc/init.d/00test
echo 'echo "Init.d is working !!!" >> /data/Test.log' >> /system/etc/init.d/00test
echo 'echo "excecuted on $(date +"%d-%m-%Y %r" )" >> /data/Test.log' >> /system/etc/init.d/00test
echo "" >> /system/etc/init.d/00test
sleep 1
echo "Creating permissive SELinux script..."
sleep 1
echo "#!/system/bin/sh" >> /system/etc/init.d/01permissive
echo "#Init.d Permissive SELinux" >> /system/etc/init.d/01permissive
echo "" >> /system/etc/init.d/01permissive
echo "busybox mount -o remount,rw -t auto /system" >> /system/etc/init.d/01permissive
echo "" >> /system/etc/init.d/01permissive
echo "setenforce 0" >> /system/etc/init.d/01permissive
echo "SELINUX=permissive" >> /system/etc/init.d/01permissive
echo "" >> /system/etc/init.d/01permissive
sleep 1
echo "Setting correct permissions and ownership for init.d folder and scipts..."
chmod 777 /system/etc/init.d
chmod 777 /system/etc/init.d/08setperm
chmod 777 /system/etc/init.d/00test
chmod 777 /system/etc/init.d/01permissive
chown 0.0 /system/etc/init.d
chown 0.0 /system/etc/init.d/08setperm
chown 0.0 /system/etc/init.d/00test
chown 0.0 /system/etc/init.d/01permissive
sleep 1
echo ""
echo "Mounting system as read-only..."
mount -o remount,ro -t auto /system
sleep 1
echo ""
echo "Done!!!"
sleep 1
echo "Please reboot at least twice before checking /data..."
sleep 1
echo "If init.d is working, you will see a Test.log in /data..."
sleep 1
echo ""
echo "Enjoy!!! =)"
echo "Ryuinferno @ XDA 2013"
exit
Приступаем к внедрению скрипта! В моем случае он будет называться init.sh.
1. Загружаем файл на sdcard мобильного устройства:
adb push /tmp/init.sh /sdcard
2. Копируем в память мобильного устройства и устанавливаем нужные права:
adb shell
su
cp /sdcard/init.sh /data/init.sh
chmod 777 /data/init.sh
3. Запускаем на выполнение:
/data/init.sh
И обращаем внимание на лог, который выводится. Вот мой лог:
Лог выполнения
Init.d Enabler by Ryuinferno @ XDA
Hello Supaa User! :P
busybox found!
Awesome! grep found! :D
Good! run-parts found! :)
Great! Let's proceed...
Press enter to continue...
Mounting system as rewritable...
Removing old sysinit file
rm: /system/bin/sysinit: No such file or directory
Checking for the presence of sysinit in /system/bin...
sysinit not found, creating file...
Setting correct permissions and ownership for sysinit...
Checking for the presence of install-recovery.sh...
install-recovery.sh not found, creating it...
Setting the correct permissions and ownership for install-recovery.sh...
Also for install-recovery-2.sh if it exists...
Checking for the presence of the init.d folder...
init.d folder found...
Creating basic init.d scripts...
Creating permissive SELinux script...
Setting correct permissions and ownership for init.d folder and scipts...
Mounting system as read-only...
Done!!!
Please reboot at least twice before checking /data...
If init.d is working, you will see a Test.log in /data...
Enjoy!!! =)
Ryuinferno @ XDA 2013
Как видим из лога, ошибок нет, поэтому смело перезагружаем устройство! Возможно у кого-то уже все заработало и вы смогли найти файл /data/Test.log, но у меня его нет. Проверим директорию /system/etc/init.d используя команду ls:
00test
01permissive
08setperm
Как видим, задачи успешно созданы. Возможно все же придется менять boot.img, но давайте в начале проверим, а где у нас файл install-recovery.sh с помощью команды
find / -name "install-recovery.sh"
...
/system/bin/install-recovery.sh
/system/etc/install-recovery.sh
...
Как можем заметить, у нас 2 файла, которые лежат в разных местах. По дате создания мы можем заметить, что скрипт создал файл в директории /system/etc/install-recovery.sh, хотя, возможно, в некоторых случаях он должен создавать его в /system/etc. Давайте переименуем файл в bin и скопируем файл из etc:
mount -o remount,rw /system
mv /install-recovery.sh /system/bin/install-recovery2.sh
cp /install-recovery.sh /system/bin/
И снова перезагружаем устройство… И вот, наконец-то долгожданный УСПЕХ! Файл /data/Test.log появился!
Раз все работает, идем в /system/etc/init.d и создаем shell-скрипт. А в нем как-раз запустим наш crond на выполнение:
echo "#!/system/bin/sh
crond -b -L /sdcard/error.log -c /data/crontab" > /system/etc/init.d/99cronstart
chmod 777 /system/etc/init.d/99cronstart
reboot
После загрузки проверяем, запустился ли crond:
ps | grep crond
root 414 1 9532 236 hrtimer_na 000dcf90 S crond
И на этом могли бы мы уже закончить, но давайте подождем минуту и посмотрим, произошла ли запись в наш файл… Ну как вы уже поняли, опять ни чего не сработало. Дело в том, что данный процесс нужно запустить от супер пользователя. Изменим скрипт в файле 99cronstart:
echo "#!/system/bin/sh
su -c crond -b -L /sdcard/error.log -c /data/crontab" > /system/etc/init.d/99cronstart
reboot
Теперь наше Android устройство поддерживает и задачи cron и может содержать shell-скрипты для автоматического запуска!
Ну и напоследок, скрипт, который будет запускать наше приложение, если его нет в процессах и сохранять информацию о том, что находилось на главном экране до запуска нашего приложения:
proc=$(ps | grep "com.test.app")
if [ "$proc" == "" ]; then
dumpsys window | grep CurrentFocus > /sdcard/current_focus.dump
sleep 1
am start -n com.test.app/com.test.app.activities.MainActivity
fi