Using Termux to Synchronise with Remote Machines (Linux/MacOS)

CacteGra
Level Up Coding
Published in
8 min readNov 21, 2020

--

Use SSH and rsync to easily share files between machines on the go

The idea is to use an Android phone as a file server so we can have our files always on us. I use a LG G5 which conveniently has an SD Card slot in which I put a 128 Gb card to store files I use daily with 4 different machines. In this article, we will take a look at how Termux, SSH, scp, and rsync can help us improve our workflow without relying on the cloud.

First things first, you have to make sure to install everything Termux-related (add-ons) through either the F-Droid app repository or Google PlayStore, but do not mix add-ons from both.

Termux only has read access to external storages (SD Cards); but using the following command, it will create an Android/data/com.termux/files/ directory where you can read and write using Termux:

termux-setup-storage

The command will also create symlinks in your Termux folder on your Android device.

Now let’s update the Termux environment and install SSH:

pkg up # or apt-get update
pkg install openssh

We want the SSH server to run anytime Termux is running. You can run a command each time you log into Termux by creating (if it does not exist) and editing the .bash_profile file within the home directory:

nano ~/.bash_profile

Here we are adding a condition to only start the SSH server and run scripts only once, where we create a file in the temporary directory that is wiped every time Termux launches and check if the file exists not to run tasks again:

#!/data/data/com.termux/files/usr/bin/bashif [ ! -e “$TMPDIR/termux-started” ]
then
sshd
touch “$TMPDIR/termux-started”
fi

I personally added another line to change the directory I work in to the one Termux has control over on SD Card. It should come outside the if the condition so that it is executed each time I log into Termux:

cd ~/storage/external-1/

Let’s focus on making SSH connections seamless (no password required).
To connect from Termux to your remote machine, create the SSH public key in Termux:

ssh-keygen -b 4096 -t rsa

Copy the key to the remote machine:

ssh-copy-id -i ~/.ssh/id_rsa.pub username@192.168.xx.xx

To connect from your remote machine to Termux, create SSH public key on your remote machine:

ssh-keygen -b 4096 -t rsa

Copy the SSH public key to Termux, to make an SSH connection to Termux, and also using the ssh-copy-id command, you need to use the Termux listening port 8022:

ssh-copy-id -p 8022 -i ~/.ssh/id_rsa.pub username@192.168.xx.xx

From now on, to connect via ssh to our Termux instance we will need only to enter the following command:

ssh -p 8022 192.168.xx.xx

Using Termux you can acces your Android device’s functionalities. You will have to install the Termux:API add-on.
In termux install the following package:

pkg install termux-api

Now you can access your battery info for example:

termux-battery-status

In order to parse the info in json form obtained through the previous command, install json parser (on macOS “brew install jq”):

pkg install jq

Now you can get what you need typing jq -r ‘.DICTIONARY_KEY’.

Let’s setup our RSYNC workflow, here is how it will work:

While the RSYNC script will run continuously, we need to check the battery status every 5 minutes and write the result in the file. Then we find the Android device’s IP on the network using the device’s mac address (it could serve as a hotspot or be on a local network), get the file from the Android device to the remote machine and move on with the transfer if there is enough left (a threshold of 20%) or the phone if plugged in, we transfer from the Android device to the remote machine and then vice versa, checking there have been changes to the folder or files.

Install rsync on Termux:

pkg install rsync

We execute the script ad vitam aeternam with:

while true
do
done

Get the battery status and write the JSON response in a .json file for later use.

echo $(termux-battery-status) > ~/storage/external-1/battery/script/folder/battery.json

Contrary to most scripts, we are not putting the script interpreter #!/bin/bash (#!/data/data/com.termux/files/usr/bin/sh in Termux) because we will be putting the interpreter into the command.

We need to create a daemon to run the script in the background. To do so, first make the script executable:

chmod +x ~/path/to/battery/script.sh

Then run the following command:

daemonize -p $PREFIX/var/run/script.pid $PREFIX/bin/bash ~/path/to/battery/script.sh

Now, you can run a command each time you log into Termux using the same .bash_profile file:

nano ~/.bash_profile

And add:

#!/data/data/com.termux/files/usr/bin/bashif [ ! -e “$TMPDIR/termux-started” ]
then
sshd
daemonize -p $PREFIX/var/run/get-battery.pid $PREFIX/bin/bash ~/storage/external-1/development/get-battery.sh
touch “$TMPDIR/termux-started”
fi

We get the IP address of our remote machine:

IP=`ifconfig en0 2>/dev/null|awk ‘/inet / {print $2}’`

Then, declare a variable to record last character of the IP string:

LAST_IP=”${IP: -1}”

And execute a loop which will remove the last character of the string each time to stop at the first ‘.’ so that we have the IP configuration of the network we are on.

while [ “${LAST_IP}” != “.” ]
do
IP=${IP%?} # Here we remove the last character of the IP string
LAST_IP=”${IP: -1}”
done

After which we map all IP address on the network through a ping scan:

echo $(nmap -sP “${IP}”0/24 >/dev/null)

We then get the IP address matching the mac address of our Android device:

IP_ADDRESS=`arp -an | grep mac:address:android:device | awk ‘{print $2}’ | sed ‘s/[()]//g’`

Our remote machine will use scp to copy the battery file over ssh to the current folder:

scp -P 8022 192.168.xx.xx:~/storage/external-1/path/to/battery/script/folder/battery.json /path/to/remote/machine/folder/

We get battery status from the battery.json file:

PLUGGED=`cat battery.json | jq -r ‘.plugged’`
PERCENTAGE=`cat battery.json | jq -r ‘.percentage’`

Check if either the device is plugged in or the percentage is high enough (here we set it at 20% battery):

OPERATIONAL=”false”
if [[ “$PLUGGED” | jq -r ‘.plugged’ == *”PLUGGED_AC”* ]]
then
OPERATIONAL=”true”
elif [[ “$PERCENTAGE” -ge “20” ]]
then
OPERATIONAL=”true”
fi

If one of the condition is true we start the synchronisation using rsync:

if [ “${OPERATIONAL}” == “true” ]
then
rsync -avr -e ‘ssh -p 8022’ — exclude-from=’/path/to/rsync-exclude.txt’ $IP_ADDRESS:~/storage/external-1 /path/to/remote/machine/folder
fi

Synchronise files from Android phone to the remote machine. We use the option — exclude-from= to use a file in which we declare which files and folders we don’t want to synchronise.

#!/bin/bashwhile true
do
IP=`ifconfig en0 2>/dev/null|awk ‘/inet / {print $2}’`
LAST_IP=”${IP: -1}”
while [ “${LAST_IP}” != “.” ]
do
IP=${IP%?}
LAST_IP=”${IP: -1}”
done
echo $(nmap -sP “${IP}”0/24 >/dev/null)
IP_ADDRESS=`arp -an | grep mac:address:android:device | awk ‘{print $2}’ | sed ‘s/[()]//g’`
scp -P 8022 “${IP_ADDRESS}”:~/storage/external-1/development/battery.json .
PLUGGED=`cat battery.json | jq -r '.plugged'`
echo "${PLUGGED}"
PERCENTAGE=`cat battery.json | jq -r '.percentage'`
echo "${PERCENTAGE}"
OPERATIONAL=”false”
if [[ $PLUGGED == *”PLUGGED_AC”* ]]
then
OPERATIONAL=”true”
elif [[ “$percentage” -ge “20” ]]
then
OPERATIONAL=”true”
fi
if [ “${OPERATIONAL}” == “true” ]
then
rsync -avr -e ‘ssh -p 8022’ — exclude-from=’/path/to/rsync-exclude.txt’ $IP_ADDRESS:~/storage/external-1/ /path/to/remote/machine/folder
fi
echo “sleep”
sleep 60s
done

When we want to synchronise from MacOS to Android, we use a similar approach, except that we first check if files and folders were modified or added in the directory we are synchronising.
First, we create a log file with the touch command to record the result of rsync and so we can easily see if past synchronisations succeeded or not.

LOGFILE=/path/to/remote/machine/folder/rsync_logs.txt
if [ ! -f “$LOGFILE” ]
then
touch “$LOGFILE”
fi

We create another file to record the date and time rsync ran successfully; because the script runs for the first time, we create a last_sync.txt file to write the date and time at which we did the last sync. We create the variable FILES that will be used in the condition checking for modified files before running rsync.

LASTSYNC=/path/to/remote/machine/folder/last_sync.txt
if [ ! -f "$LASTSYNC" ]
then
touch "$LASTSYNC"
FILES="first time"
else
LASTDATE=`cat "${LASTSYNC}"`
echo "${LASTDATE}"
LASTDATE=`date -r "$LASTDATE" +"%s"`
echo "${LASTDATE}"
now=`date +%s`
echo ${NOW}
PERIOD=$(( ($NOW - $LASTDATE) / 60 ))
echo ${PERIOD}
FILES=`find /path/to/remote/machine/folder -mmin -"${PERIOD}" | sort -n -r`
fi

If the file already exists, we get the last date and time from the file using “cat” and then parse the date as unix epoch time.

LASTDATE=`cat “${FILE}”`
LASTDATE=`date -r “$LASTDATE” +”%s”`

Then we subtract the current unix epoch time with the last date and time and get minutes:

PERIOD=$(( ($now — $last_date) / 60 ))

Now, let’s use the “find” command to check changes inside the sync folder. The following command will return a list of all changes inside the folder as well as the synchronised folder all files ordered by size.

FILES=`find ~/remote_machine_sync_folder -type f -mmin -”${period}”`

If the files variable is not null, we run rsync and then check if the command succeeded (when we write the date and time it did) or not and log the result into a rsync_logs.txt file:

if [[ $SYNCING -eq 0 ]]
then
echo “${SYNCING}” > “${LOGFILE}”
echo `date +%s > “${LASTSYNC}”`
echo “success and sleep”
sleep 10s
else
echo “${SYNCING}” > “${LOGFILE}”
echo “fail and sleep”
sleep 10s
fi

And finally, we write the date and time to the last_sync.txt file.

Synchronise files from MacOS to Android

#!/bin/bashwhile true
do
LOGFILE=/path/to/remote/machine/folder/rsync_logs.txt
if [ ! -f “$LOGFILE” ]
then
touch “$LOGFILE”
fi
LASTSYNC=/path/to/remote/machine/folder/last_sync.txt
if [ ! -f “$LASTSYNC” ]
then
touch “$LASTSYNC”
FILES=”first time”
else
LASTDATE=`cat “${LASTSYNC}”`
echo “${LASTDATE}”
LASTDATE=`date -r “$last_date” +”%s”`
echo “${LASTDATE}”
NOW=`date +%s`
echo ${NOW}
period=$(( ($NOW — $LASTDATE) / 60 ))
echo ${period}
FILES=`find /path/to/remote/machine/folder -mmin -”${period}” | sort -n -r`
fi
if [ “${FILES}” ]
then
IP=`ifconfig en0 2>/dev/null|awk ‘/inet / {print $2}’`
LAST_IP=”${IP: -1}”
while [ “${LAST_IP}” != “.” ]
do
IP=${IP%?}
LAST_IP=”${IP: -1}”
done
echo $(nmap -sP “${IP}”0/24 >/dev/null)
IP_ADDRESS=`arp -an | grep mac:address:android:device | awk ‘{print $2}’ | sed ‘s/[()]//g’`
scp -P 8022 “${IP_ADDRESS}”:~/storage/external-1/development/battery.json /path/to/remote/machine/folder/
PLUGGED=`cat battery.json | jq -r '.plugged'`
echo "${PLUGGED}"
PERCENTAGE=`cat battery.json | jq -r '.percentage'`
echo "${PERCENTAGE}"
OPERATIONAL=”false”
if [[ $PLUGGED == *”PLUGGED_AC”* ]]
then
OPERATIONAL=”true”
elif [[ “$percentage” -ge “20” ]]
then
OPERATIONAL=”true”
fi
if [ “${OPERATIONAL}” == “true” ]
then
rsync -avr -e ‘ssh -p 8022’ — exclude-from=’/path/to/rsync-exclude.json’ /path/to/remote/machine/sync/folder $IP_ADDDRESS:~/storage/external-1
SYNCING=$?
if [[ $SYNCING -eq 0 ]]
then
echo “${SYNCING}” > “${LOGFILE}”
echo `date +%s > “${LASTSYNC}”`
echo “success and sleep”
sleep 10s
else
echo “${SYNCING}” > “${LOGFILE}”
echo “fail and sleep”
sleep 10s
fi
fi
fi
done

To run these scripts on the remote machines we might also use daemonize, or we can use crontab that will execute the scripts for us at a given time or period of time.

First remove the while loop set in the scripts. In Linux and macOS, give rights to the script:

chmod u+x /path/to/remote_to_android.sh

Schedule the script using cron, type in the terminal:

crontab -e

Then type i to edit the file:

10 * * * * /path/to/remote_to_android.sh

Each * corresponds to:
minute(s) hour(s) day(s) month(s) weekday(s)
By writing 10 * * * * the script will run at the tenth minute of every hour.
By writing */10 * * * * the script will run every ten minutes of an hour.

If you want to get logs from the echo we make in the script. Logs will be found in /tmp/, stdout.log to get echo commands and stderr.log for errors:

10 * * * * /path/to/remote_to_android.sh >/tmp/stdout.log 2>/tmp/stderr.log

--

--

It's all about interweaving words and code to create something new.