Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

F-droid partial mirror

I want to ensure the longevity of the tools I use on my mobile devices. Of course I use the amazing F-droid app store. I wanted to make sure the packages I use from this repository are available even if F-droid ever disappears. (A secondary benefit is that if I ever reinstall, or update, apps from my local repo, the downloads are way faster!)

So I spent a bunch of time reading the docs and learning how to set up my own, partial binary mirror of F-droid. A future step might include building packages, but for now I'm sticking to just the binaries.

Preparing the main F-droid custom mirror

All work is done on server3.

sudo useradd fdroid # set pw in keepass
sudo mkdir -p /mnt/mirror/fdroid /opt/android-sdk
sudo dnf install java-1.8.0-openjdk-headless
sudo dnf install --setopt=install_weak_deps=False java-1.8.0-openjdk-devel

The bare command would be this:

sudo -u fdroid -E /usr/bin/rsync -n -aHS --delete --delete-delay --info=progress2 plug-mirror.rcac.purdue.edu::fdroid/repo/ /mnt/mirror/fdroid/

But I of course wrote a script for myself, which is shown farther down in this post.

So, back to the commands for initial setup.

sudo su - fdroid
python3 -m venv ~/venv1
source ~/venv1/bin/activate
pip install --upgrade pip
pip install fdroidserver cffi bcrypt setuptools-rust androguard==3.3.5
mkdir -p ~/git
git clone https://gitlab.com/fdroid/fdroiddata ~/git/fdroiddata

The androguard version is important: it needs to be less than 4.0 for a reason that hopefully is documented in one of my references. So 3.3.5 was the last in the <3.6 or <4.0 series, and that higher version breaks something somewhere.

Preparing the keystore with my official cert for server3:

openssl pkcs12 -export -in /etc/pki/tls/certs/https-server3.ipa.internal.com.pem -inkey /etc/pki/tls/private/https-server3.ipa.internal.com-nopw.key -out /etc/pki/tls/private/https-server3.ipa.internal.com.p12 -name server3 -CAfile /etc/ipa/ca.crt -caname ipa
# entered the password, in keepass
sudo chown "${admin1}:fdroid" /etc/pki/tls/private/https-server3.ipa.internal.com.p12
sudo chmod 0640 /etc/pki/tls/private/https-server3.ipa.internal.com.p12

Updated keystore, keystorepass, keypass, repo_keyalias in config.yml.

files/2024/listings/fdroid-sync.sh (Source)

  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
#/bin/sh
# vim: set ts=3 sw=3 sts=3:
# File: /etc/installed/fdroid/fdroid-sync.sh
# Location: server3
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only
# Startdate: 2024-05-29-4 07:36
# Title: F-Droid Sync wrapper
# Purpose: Single command to mirror down only the F-droid packages I use
# History:
# Usage:
# Reference:
# Improve:
#    take a list of hardcoded filenames and prepend them with "P" in the rsync file, to avoid deletion.
# Documentation:
# Dependencies:
#    user fdroid, ~/venv1, custom pip packages installed
#    cd ~/git ; git clone https://gitlab.com/fdroid/fdroiddata
#    plecho from bgscripts

CONF_FILE="${CONF_FILE:-/etc/sysconfig/fdroid-sync}"
test -f "${CONF_FILE}" && . "${CONF_FILE}"
WORK_DIR="${WORK_DIR:-/etc/installed/fdroid}"
GET_FILE="${GET_FILE:-${WORK_DIR}/get}"
MIRROR_DIR="${MIRROR_DIR:-/mnt/mirror/fdroid/repo}"
GIT_DIR="${GIT_DIR:-/home/fdroid/git/fdroiddata}"
INSTALLED_FILES_DIR="${INSTALLED_FILES_DIR:-/mnt/public/Support/Systems/server3/fdroid}"

prepare_input_file() {
   echo "# BEGIN preparing list of packages I want."
   # installed.txt was the exact output shared from my fdroid instance(s)
   # I do not understand how this appears to limit to just our desired apps, within the icons/ path while also grabbing still the app files. But I think it does, so we will leave it at that.
   {
      #awk -F',' '$1!~/packageName/{print $1}' < "${WORK_DIR}/installed.txt" | awk '{print "+ **/"$0"**";}'
         #awk -F',' '$1!~/packageName/{print "+ **/"$1"*"; print "+ **/"$1"/en*/***"; print "+ icons*/"$1"***";}' | sort
      printf '%s\n' '- org.fdroid.fdroid.privileged*' ;
      printf '%s\n' '- com.termux.api*' ;
      printf '%s\n' '- com.termux.boot*' ;
      printf '%s\n' '- com.termux.gui*' ;
      printf '%s\n' '- com.termux.nix*' ;
      printf '%s\n' '- com.termux.styling*' ;
      printf '%s\n' '- com.termux.tasker*' ;
      printf '%s\n' '- com.termux.widget*' ;
      printf '%s\n' '- com.termux.window*' ;
      # Find all the metadata/*.protect files and use that filename as a deletion-protection filter for rsync
      # Does this, in effect: printf '%s\n' 'P org.qtproject.bibletimemini*' ;
      find "${MIRROR_DIR%%/}/../metadata/"*.protect | \
         xargs basename --multiple | sed -r -e 's/\.protect$/*/;' -e 's/^/P /;'
      cat "${INSTALLED_FILES_DIR%%/}"/installed*.txt | grep -vE 'com.google.android.marvin.talkback|org.fdroid.fdroid.privileged|\<packageName\>' | \
         awk -F',' '$1!~/packageName/{print "+ **/"$1"*"; print "+ **/"$1"/en*/***"; }' | sort
      printf '%s\n' '+ icons*/'
      printf '%s\n' '- *' ;
      #printf '%s\n' '+ **/index*'
      #awk -F',' '$1!~/packageName/{print $1}' < installed.txt | awk '{print "+ **"$0"*";print "+ icons*/"$0"*";}' ; printf '%s\n' '- *' ;
   } > "${GET_FILE}"
}

sync_files() {
   echo "# BEGIN syncing files"
   if test -n "${APPLY}" ;
   then
      dashn=""
   else
      dashn="-n "
   fi
   if test "${USER}" != "fdroid" ;
   then
      sudo -u fdroid -E /usr/bin/rsync --filter "merge ${GET_FILE}" ${dashn}-v -r -d -aHS --delete --delete-delay plug-mirror.rcac.purdue.edu::fdroid/repo/ "${MIRROR_DIR}"
   else
      /usr/bin/rsync --filter "merge ${GET_FILE}" ${dashn}-v -r -aHS --delete --delete-delay plug-mirror.rcac.purdue.edu::fdroid/repo/ "${MIRROR_DIR}"
   fi
}

fix_metadata() {
   echo "# BEGIN fixing metadata"
   _oldpwd="${PWD}"
   cd "${GIT_DIR}"
   git checkout master ; git pull
   cat "${INSTALLED_FILES_DIR%%/}"/installed*.txt | grep -vE 'com.google.android.marvin.talkback|org.fdroid.fdroid.privileged|\<packageName\>' | \
   awk -F',' '{print $1}' | while read app ;
   do
      appyml="${app}.yml"
      destfile="$( find "${MIRROR_DIR%%/}/../metadata/" -mindepth 1 -maxdepth 1 -name "${appyml}" -exec readlink -f {} \; )"
      test -z "${destfile}" && {
         destfile="${MIRROR_DIR%%/}/../metadata/${appyml}"
         echo "Making new file ${destfile}"
         touch "${destfile}"
      }
      descfile="${destfile%%.yml}.desc"
      srcfile="$( find "${GIT_DIR}/metadata" -name "${appyml}" -exec readlink -f {} \; )"
      echo "Need to munge ${srcfile} to ${destfile}"
      test -f "${descfile}" || { curl --silent -L "https://f-droid.org/en/packages/${app}/" | awk -F'"' '/meta name.*description/{print $4}' > "${descfile}" ; }
      if test -f "${srcfile}" ;
      then
         cp -pf "${srcfile}" "${destfile}"
         # too simple, and just one line. this should be rewritten in python bs4
         echo "Summary: '$( cat "${descfile}" )'" >> "${destfile}"
      fi
      sed -i -r -e "/ArchivePolicy:/d" -e "/AutoName:/s/^AutoName:/Name:/g;" "${destfile}"
   done
   cd "${_oldpwd}"
}

fdroid_update() {
   (
      source ~/venv1/bin/activate
      cd "${MIRROR_DIR}/.."
      fdroid update -c --use-date-from-apk
   )
}

generate_web() {
   /etc/installed/fdroid/fdroid_generate_web.py
}

main() {
   cd "${WORK_DIR}"
   test -z "${SKIP_INPUT}" && prepare_input_file
   test -z "${SKIP_METADATA}" && fix_metadata
   test -z "${SKIP_SYNC}" && sync_files
   test -z "${SKIP_UPDATE}" && fdroid_update
   test -z "${SKIP_WEB}" && generate_web
}

case "${0}" in
   "-bash")
      # dot-sourced
      :
      ;;
   *)
      main | plecho "fdroid_sync"
      ;;
esac

Preparing the repo, after using the above custom rsync process in /etc/installed/fdroid-sync.sh.

sudo su - fdroid
cd /mnt/mirror/fdroid
time fdroid init --android-home /opt/android-sdk -v

Had to add java_paths to config.yml after the init:

/usr/lib/jvm/jre-openjdk/

Using a custom icon was tricky. The program likes to warn about file repo/icons/icon.png, but the file needs to be explicitly named in config.yml, and stored in /mnt/mirror/fdroid/ directly.

cp -pi /mnt/public/Support/Programs/f-droid/bgstack15.png /mnt/mirror/fdroid/bgstack15.png

Using this fdroid mirror

Visit https://www.example.com/mirror/fdroid/repo or http://server3/mirror/fdroid/repo in a browser. This sync process could never happen again, but what is already here will remain useful.

Operations

Some expected operations are described here.

Syncing the mirror

Use the custom script /etc/installed/fdroid/fdroid-sync.sh. You can pass various SKIP_ parameters:

  • SKIP_INPUT Skip customizing the list of packages from my installed*.txt raw input from mobile devices.
  • SKIP_METADATA Skip updating the metadata yml files with descriptions of the apps. If I have not changed apps or have any reason to expect the descriptions or screenshots have changed, I can skip the metadata.
  • SKIP_SYNC Skip main rsync process.
  • SKIP_UPDATE Skip the fdroid repo update process.
  • SKIP_WEB Skip the simple web index.html generation.

This is configured in cron, weekly on server3.

Updating list of used packages

You can use F-droid to share a list of packages and version numbers installed. Export this list to a text file, /etc/installed/fdroid/installed-${DEVICENAME}.txt. Note that in order to list packages, you must have the repository on from where the packages were installed. That is, if Jellyfin is only in the F-droid main repo, but you disable this repo, the f-droid client will not list Jellyfin as installed.

Rerun fdroid-sync.sh which will automatically grab all installed*.txt files in /etc/installed/fdroid/.

Dependencies

Storage3 is AlmaLinux 8, so it has python 3.6 and pip. A virtual environment had some packages installed, to ~fdroid/venv1. This gets activated by the steps that need it in fdroid-sync.sh

Android SDK was installed, and a command was used to get certain components of that. It is possible that only apksigner is required.

Related files

  • /etc/installed/fdroid/fdroid-sync.sh
  • can be dot-sourced for interactively running any individuals steps.
  • /etc/installed/fdroid/fdroid_generate_web.py (next blog post)
  • can be run independently. Builds the index.html for /mnt/mirror/fdroid/, not repo/ which fdroid update handles from inside fdroid-sync.sh.
  • /etc/sysconfig/fdroid-sync optional config file
  • /etc/sysconfig/fdroid-generate-web optional config file
  • /etc/installed/fdroid/get dynamically generated by fdroid-sync.sh
  • /mnt/public/Support/Systems/server3/fdroid/installed-*.txt lists of my installed packages, exported from F-droid on each of my devices.
  • /mnt/mirror/fdroid/config.yml generated by fdroid init and then customized
  • /mnt/mirror/fdroid/fdroid.css small hand-crafted css for my index.html.
  • /etc/pki/tls/private/https-server3.ipa.internal.com.p12 pfx generated from the official IPA cert for server3.
  • /etc/cron.d/mirror_cron has a line for running this weekly.

    0 14 * * 4 fdroid /etc/installed/fdroid/fdroid-sync.sh 1>/dev/null 2>&1

Previous files

  • /mnt/mirror/fdroid/keystore.p12 generated by fdroid init. Password is in keepass. This file was replaced with

Improvements

I might need to update the android-sdk thing somehow, or I might decide I want to try to compile packages.

Installing the Android SDK

The Android SDK provides the apksigner utility used by fdroid update. It's technically optional, but it makes the signing "stronger" or something.

sudo mkdir -p /opt/android-sdk
sudo chown fdroid:admins -R /opt/android-sdk
sudo su - fdroid
cd /opt/android-sdk
cd /opt/wg
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
unzip commandlinetools-*latest.zip
# ran into an error about class file version 61.0, so need java-17-openjdk-headless

As an admin:

sudo dnf install java-17-openjdk-headless

Back as user fdroid:

time JAVA_HOME=/usr/lib/jvm/jre-17 /opt/android-sdk/cmdline-tools/bin/sdkmanager "platform-tools" "build-tools;27.0.3" "ndk-bundle" --sdk_root=/opt/android-sdk
# had to say "y" to the google license
time JAVA_HOME=/usr/lib/jvm/jre-17 /opt/android-sdk/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android-sdk --update
time JAVA_HOME=/usr/lib/jvm/jre-17 /opt/android-sdk/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android-sdk "build-tools;34.0.0"

References

  1. Running a Mirror | F-Droid - Free and Open Source Android App Repository
  2. Installing the Server and Repo Tools | F-Droid - Free and Open Source Android App Repository
  3. Setup an F-Droid App Repo | F-Droid - Free and Open Source Android App Repository
  4. examples/config.yml · 2.1.2 · F-Droid / fdroidserver · GitLab
  5. GitHub Actions 101 Continued: How to Self-Host a F-Droid Repo – Vinfall@Geekademy

Android SDK references

  1. Install Android Studio on Rocky Linux 8|AlmaLinux 8|CentOS 8 - TechViewLeo
  2. This document describes how to install Android SDK on a headless Linux server, i.e. a server with no GUI, just a shell. This proved very useful when I had to set up Android builds for Continuous Integration.
  3. Download Android Studio & App Tools - Android Developers
  4. https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip

Comments