# apksigner.py - An APK Signing Service Provides code signing for APKs. ## Prerequisites The service needs an SFTP server for exchanging data with the clients. Additionally, for signing APKs you need - a key store file containing the certificate to sign the APKs with, - a file containing the password to unlock the key store. ## Installation ### Install an Android SDK `apksigner.py` needs the tools `apksigner`, `zipalign`, and `apkanalyzer` from an Android SDK. The following instructions are based on the installation of the Android SDK in KDE's Android Docker image. See below for an alternative setup using KDE's Android Docker image. ```sh export NDK_VERSION=22.1.7171670 export SDK_PLATFORM=android-33 export SDK_BUILD_TOOLS=30.0.3 export SDK_PACKAGES="tools platform-tools" export ANDROID_HOME=~/android-sdk export ANDROID_SDK_ROOT=${ANDROID_HOME} export PATH=${ANDROID_HOME}/cmdline-tools/tools/bin:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} curl -Lo /tmp/sdk-tools.zip 'https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip' mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools unzip -q /tmp/sdk-tools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools rm -f /tmp/sdk-tools.zip yes | sdkmanager --licenses sdkmanager --verbose "platforms;${SDK_PLATFORM}" "build-tools;${SDK_BUILD_TOOLS}" "ndk;${NDK_VERSION}" ${SDK_PACKAGES} sdkmanager --uninstall --verbose emulator ``` ### Install the Service Clone this repository on a server that shall provide the service, create a virtual environment, and install the requirements. ```sh git clone https://invent.kde.org/sysadmin/ci-notary-service.git apksigner cd apksigner python3 -m venv .venv . .venv/bin/activate pip3 install -r requirements.txt ``` ### Configure the Service Copy `apksigner.sample.ini` to `apksigner.ini` and replace the example values with the appropriate values for your installation. Then copy `apksigner-projects.sample.yaml` to `apksigner-projects.yaml` and specify the settings for the projects you want to sign APKs for. ## Run the Service Activate the virtual environment and start `apksigner.py`. ```sh cd ~/apksigner . .venv/bin/activate python3 apksigner.py --id workerA --config apksigner.ini ``` where you replace `workerA` with a unique alpha-numeric ID for each instance of `apksigner.py` you start. ## Use the Service to Sign an APK Clone this repository on the client, create a virtual environment, and install the requirements. ```sh git clone https://invent.kde.org/sysadmin/ci-notary-service.git apksigner cd apksigner python3 -m venv .venv . .venv/bin/activate pip3 install -r requirements.txt ``` Copy `signapk.sample.ini` to `signapk.ini` and replace the example values with the appropriate values for your installation. In particular, the value for `BasePath` must match the corresponding value in the settings of the service. Then run `signapk.py` to sign the APK ```sh python3 signapk.py --config signapk.ini ``` ## Managing the Service with systemd We set up the service under the user's control with a per-user systemd instance. Copy `apksigner-user.sample.service` to `~/.config/systemd/user/apksigner-user.service` and modify it as needed. The example uses the username as ID of the service worker. Make sure that the ID is unique if you run multiple service workers. Start the service with ```sh systemctl --user start apksigner-user.service ``` If you want the service to be started automatically, then enable autostart with ```sh systemctl --user enable apksigner-user.service ``` Note: By default, the systemd user instance and thus the user-managed services are started after the first login of a user and killed after the last session of the user is closed. To start the user instance right after boot, and keep the systemd user instance running after the last session closes you have to enable lingering for the user: ```sh loginctl enable-linger USERNAME ``` For more details on user-managed services see https://wiki.archlinux.org/title/Systemd/User. ## Running the Service with KDE's Android Docker Image We assume that you have already created a suitable `apksigner.ini` and `apksigner-projects.yaml` and put them in the working copy of this repository. The correct values for `apksigner.ini` for KDE's Android Docker image are ```ini [ApkSigning] KeyStore = /keystore.jks KeyStorePass = file:/keystorepass SDKToolsPath = /opt/android-sdk/build-tools/30.0.2 APKAnalyzerPath = /opt/android-sdk/cmdline-tools/tools/bin/apkanalyzer [General] ProjectSettings = apksigner-projects.yaml ``` Start a Docker container from inside the working copy of this repository passing in the working copy, the `known_hosts` file, the files with the KeyStore and the password to unlock the KeyStore, and the socket of the SSH agent: ```sh docker run -it --rm \ -v $(pwd):/apksigner \ -v :/home/user/.ssh/known_hosts \ -v :/keystore.jks -v :/keystorepass \ -v $SSH_AUTH_SOCK:/ssh-agent --env SSH_AUTH_SOCK=/ssh-agent \ invent-registry.kde.org/sysadmin/ci-images/android-qt515 ``` Then inside the container run: ```sh python3 -m venv ~/.venv . ~/.venv/bin/activate cd /apksigner pip3 install -r requirements.txt python3 apksigner.py --id workerA --config apksigner.ini ``` ### Details To protect the private SSH key we forward the SSH agent into the Docker container. Alternatively, you can mount a file holding a private OpenSSH RSA key into the container.