workflow: rules: # required to enable merge request pipelines: https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html#prerequisites - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_ENABLE_MERGE_REQUEST_PIPELINES != "false" # to prevent duplicate pipelines in MRs: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" && $CI_ENABLE_MERGE_REQUEST_PIPELINES != "false" when: never - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH stages: - prepare - build - test - deploy variables: pip: pip3 --timeout 100 --retries 10 GIT_DEPTH: description: needs lots of git history since it has to compare the merge request to current master value: "5000" CI_ENABLE_MERGE_REQUEST_PIPELINES: description: push with `git push -o ci.variable="CI_ENABLE_MERGE_REQUEST_PIPELINES=false"` to disable merge request pipelines (e.g., to use self-hosted runners) value: "true" .install_fdroid_server: &install_fdroid_server - rm -rf $fdroidserver - mkdir $fdroidserver - git ls-remote https://gitlab.com/fdroid/fdroidserver.git master - curl --silent https://gitlab.com/fdroid/fdroidserver/-/archive/master/fdroidserver-master.tar.gz | tar -xz --directory=$fdroidserver --strip-components=1 - export PATH="$fdroidserver:$PATH" - export PYTHONPATH="$fdroidserver:$fdroidserver/examples" - export PYTHONUNBUFFERED=true # avoid error from the env var in config.yml - export serverwebroot=/tmp .install_fdroid_server_deb: &install_fdroid_server_deb - apt-get --allow-releaseinfo-change update - apt-get -qy dist-upgrade - echo "locales locales/locales_to_be_generated multiselect en_US.UTF-8 UTF-8" | debconf-set-selections - apt-get install -qy --no-install-recommends fdroidserver apksigner mercurial git git-svn brz python3-launchpadlib locales curl .set_up_test_keystore: &set_up_test_keystore - cp -a $fdroidserver/tests/gnupghome $fdroidserver/tests/keystore.jks $CI_PROJECT_DIR/ - grep '^key.*pass' $fdroidserver/tests/config.yml | sed 's,\x3a ,=,' > $CI_PROJECT_DIR/variables - | tee --append $CI_PROJECT_DIR/variables < /dev/null || { set_error; printf "\nThese files have lint issues:\n"; git diff --name-only; printf "\nThese are the formatting issues:\n"; printf "$(git --no-pager diff --color=always --ws-error-highlight=all | sed -e 's/\r/^M/' -e 's/%/%%/g')"; } fi - exit $EXITVALUE fdroid lint config: stage: test needs: [] image: debian:bookworm-slim before_script: - apt-get -qy update - apt-get -qy dist-upgrade - apt-get -qy install --no-install-recommends ca-certificates curl git python3-asn1crypto python3-defusedxml python3-git python3-pycountry python3-qrcode python3-requests python3-ruamel.yaml yamllint - export fdroidserver=$CI_PROJECT_DIR/fdroidserver - *install_fdroid_server rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: paths: - .gitlab-ci.yml - config.yml - config/*.yml - config/*/*.yml # only check pushes to fdroids own repo - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_PATH == "fdroid/fdroiddata" changes: compare_to: 'refs/heads/master' paths: - .gitlab-ci.yml - config.yml - config/*.yml - config/*/*.yml script: - chown -R $(whoami) . - chmod 0600 config.yml config/*.yml config/*/*.yml - export gpghome=foo keystore=foo keystorepass=foo keypass=foo serverwebconfig=foo - fdroid lint config.yml config/*.yml config/*/*.yml # Alternate English locales must include the country code, e.g. en-GB. - python3 -c "import os; exit(os.path.exists('config/en/'))" lint: stage: test needs: [] image: debian:bookworm-slim rules: *app_verification_rules script: - apt-get -qy update - apt-get -qy dist-upgrade - apt-get -qy install --no-install-recommends exiftool git jq python3 python3-yaml - export EXITVALUE=0 - function set_error() { export EXITVALUE=1; printf "\x1b[31mERROR `history|tail -2|head -1|cut -b 6-500`\x1b[0m\n"; } - chown -R $(whoami) . - ./tools/check-exif-in-images.sh || set_error - ./tools/check-localized-metadata.py || set_error - ./tools/check-keyalias-collision.py || set_error - ./tools/check-metadata-summary-whitespace.py || set_error - ./tools/check-for-unattached-signatures.py || set_error - ./tools/make-summary-translatable.py || set_error # GitLab only supports one codequality report per job - jq '[.[]|.[]]' --slurp *.json > codequality.json - exit $EXITVALUE artifacts: reports: codequality: codequality.json schema validation: stage: test needs: [] image: python rules: *app_verification_rules before_script: - pip install check-jsonschema script: - chown -R $(whoami) . - check-jsonschema --check-metaschema schemas/metadata.json || exit 1 - *get_target_source_refs - git diff --exit-code --name-only "${TARGET_REF}...${SOURCE_REF}" schemas/metadata.json || { check-jsonschema --schemafile schemas/metadata.json metadata/*.yml || exit 1; exit; } - set -xe - *get_changed_apps - if [ -z "$CHANGED" ]; then exit; fi - echo $CHANGED | sed -En 's|([^ ]*)|metadata/\1.yml|gp' | xargs check-jsonschema --schemafile schemas/metadata.json trigger-issuebot: stage: prepare needs: [] when: manual image: alpine variables: GIT_DEPTH: "1" artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - logs/ when: always expire_in: 1 week script: - chown -R $(whoami) . - mkdir logs - export | grep -F CI_ | grep -vFi -e password -e token > logs/export.txt - apk add --no-cache bash curl - ./tools/trigger-issuebot # This job should be as close as possible to the production # buildserver. It should not include custom setup or methods, except # when there is no other way to make this job work. The docker image # is created using the same provisioning as the production buildserver, # via the "docker" job in fdroid/fdroidserver. # # This job is tied to the buildserver. Changes must be reviewed by # the buildserver maintainers. fdroid build: stage: build needs: [] tags: - saas-linux-medium-amd64 # Some env vars are setup in https://gitlab.com/fdroid/fdroidserver/-/blob/master/buildserver/setup-env-vars # home_vagrant=/home/vagrant image: registry.gitlab.com/fdroid/fdroidserver:buildserver-bookworm artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - repo/ - unsigned/ - tmp/ when: always expire_in: 1 month rules: *app_verification_rules cache: key: "$CI_JOB_NAME" paths: - .gradle variables: ANDROID_HOME: /opt/android-sdk script: - chown -R $(whoami) . - test -d build || mkdir build - *get_changed_builds - test -n "$(printf "$CHANGED" | tr -d '[:space:]')" || { echo "no packages need processing, exiting"; exit 0; } - apt-get update - apt-get dist-upgrade - test -n "$fdroidserver" || source /etc/profile.d/bsenv.sh # These packages are needed to make this env like the production buildserver. - sdkmanager "platform-tools" "build-tools;31.0.0" - *install_fdroid_server - git -C $home_vagrant/gradlew-fdroid pull # Set up git config something like the production buildserver. The # difference is that in production, this is installed on the # buildserver VM host, while here it is basically in the # buildserver VM. # https://gitlab.com/fdroid/fdroid-bootstrap-buildserver/-/merge_requests/30 - curl --silent 'https://gitlab.com/fdroid/fdroid-bootstrap-buildserver/-/raw/master/roles/production_hardening/files/gitconfig' >> $CI_PROJECT_DIR/../.gitconfig - for d in logs tmp unsigned $home_vagrant/.android $home_vagrant/.gradle $home_vagrant/metadata; do test -d $d || mkdir $d; chown -R vagrant $d; done # make sure .gradle stores inside $CI_PROJECT_DIR to make it cacheable - ln -s $home_vagrant/.gradle $CI_PROJECT_DIR/.gradle - ln -s $CI_PROJECT_DIR/tmp $home_vagrant/tmp - ln -s $CI_PROJECT_DIR/srclibs $home_vagrant/srclibs # Some builds need to track a lot of files, so this needs to be # set for them to succeed. Some runners do not support setting # sysctl, so it cannot be required. - sysctl fs.inotify.max_user_watches=524288 || true - export GRADLE_USER_HOME=$home_vagrant/.gradle # each `fdroid build --on-server` run expects sudo, then uninstalls it - export fdroid="sudo --preserve-env --user vagrant env PATH=$fdroidserver:$PATH env PYTHONPATH=$fdroidserver:$fdroidserver/examples env PYTHONUNBUFFERED=true env TERM=$TERM env HOME=$home_vagrant fdroid" # use fdroidserver test keystore as placeholder since `fdroid publish` requires it - export keystorepass='r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI=' - export keypass='r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI=' - export keystore=$fdroidserver/tests/keystore.jks - keytool -changealias -alias sova -destalias ciarang -keystore $keystore -storepass:env keystorepass -keypass:env keypass - APPS="$(./tools/find-changed-builds.py)" - for build in $APPS; do set -x; apt-get install sudo; cp -R $CI_PROJECT_DIR/build $home_vagrant/build; appid=${build%:*}; [ -d metadata/$appid ] && cp -R metadata/$appid $home_vagrant/metadata; cp -R metadata/$appid.yml $home_vagrant/metadata; chown -R vagrant $home_vagrant $CI_PROJECT_DIR; pushd $home_vagrant; ln -s $CI_PROJECT_DIR $home_vagrant/fdroiddata; ln -s $CI_PROJECT_DIR/../.gitconfig $home_vagrant/.gitconfig; $fdroid fetchsrclibs $build --verbose; rm $home_vagrant/fdroiddata $home_vagrant/.gitconfig; (unset CI; $fdroid build --verbose --test --refresh-scanner --on-server --no-tarball $build); apt-get install sudo; popd; rm -rf $home_vagrant/build || true; rm -rf $ANDROID_HOME/ndk || true; apt-get install -y openjdk-17-jdk-headless; update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java; ./tools/build-contains-signatures.py $build || continue; $fdroid publish --verbose --error-on-failed $build; done - ./tools/audit-gradle.py $CHANGED; check apk: stage: test dependencies: - fdroid build image: debian:trixie-slim rules: *app_verification_rules variables: ANDROID_HOME: /opt/android-sdk before_script: - ls tmp/*.apk 2> /dev/null || exit 0 - apt-get update - apt-get install -qy --no-install-recommends fdroidserver curl - export fdroidserver=$PWD/fdroidserver - *install_fdroid_server - sdkmanager "build-tools;36.0.0" script: - set -o pipefail - chown -R $(whoami) . - export EXITVALUE=0 - | function generate_report() { echo "{\"location\": { \ \"path\": \"metadata/$1.yml\", \ \"lines\": {\"begin\": $2}}, \ \"description\": \"$3\", \ \"check_name\": \"$4\", \ \"fingerprint\": \"$5\", \ \"severity\": \"$6\"},"; } - echo '[' > codequality.json - | for apk in tmp/*.apk; do appid=$(echo $apk | sed -n 's|tmp/\(.*\)_[0-9]\+\.apk$|\1|p'); vercode=$(echo $apk | sed -n 's|tmp/.*_\([0-9]\+\)\.apk$|\1|p'); location=$(($(grep -n "^ versionCode: $vercode$" metadata/$appid.yml | cut -f1 -d:) - 1)); binary=$(echo $apk | sed -e 's|tmp/|tmp/binaries/|' -e 's/\.apk$/.binary.apk/'); if [[ -e $binary ]]; then file=$binary; else file=$apk; fi fdroid scanner --verbose --exit-code $file 2>&1 | tee result || { export EXITVALUE=1; for class in $(sed -n "s/.*DEBUG: Problem: found class '\(.*\)'/\1/p" result); do printf "\x1b[31mERROR Found $class in $file\x1b[0m\n"; generate_report $appid $location "Found class $class" "class" "$file $class" "critical" >> codequality.json; done grep 'Dependency metadata' result && { printf "\x1b[31mERROR Found extra signing block 'Dependency metadata' in $file\x1b[0m\n"; generate_report $appid $location "Found Dependency metadata" "signing_block" "$file Dependency metadata" "minor" >> codequality.json; }; }; androguard axml $file -o AndroidManifest.xml for permission in $(sed -n 's|.*> codequality.json; done grep 'android:usesCleartextTraffic="true"' AndroidManifest.xml && \ generate_report $appid $location "Cleartext Traffic Permitted" "application_attribution" "$file cleartextTrafficPermitted" "major" >> codequality.json; grep 'android:debuggable="true"' AndroidManifest.xml && \ generate_report $appid $location "Debuggable APK" "application_attribution" "$file debuggable" "critical" >> codequality.json; grep 'android:testOnly="true"' AndroidManifest.xml && \ generate_report $appid $location "Testing APK" "application_attribution" "$file testOnly" "critical" >> codequality.json; done - sed -i -e '$s/,$/]/' codequality.json - exit $EXITVALUE artifacts: reports: codequality: codequality.json # issuebot needs secrets to run, so it has to run under the 'fdroid' # group, therefore needs the trigger without secrets, there would be # no support for virustotal, github downloads, exodus privacy checks, # etc. That means it has to be triggered from the lint: job, which # runs in the fork's CI. The trigger-issuebot job adds the env var # FROM_CI_MERGE_REQUEST_IID which is required to have this job be # triggered. # # This job has to be called 'pages' because it deploys the JSON API # to GitLab Pages. pages: stage: deploy tags: - saas-linux-medium-amd64 image: registry.gitlab.com/fdroid/fdroidserver:buildserver-bookworm only: refs: - branches variables: - $FROM_CI_MERGE_REQUEST_IID artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_JOB_ID}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - metadata/ - public/ - repo/index-v1.json - repo/index.xml - tmp/apkcache.json - unsigned/ when: always script: - apt-get update - apt-get dist-upgrade - apt-get install python3-github python3-gitlab python3-defusedxml python3-javaproperties python3-requests-cache python3-venv python3-wheel - test -n "$fdroidserver" || source /etc/profile.d/bsenv.sh - *install_fdroid_server - export GRADLE_USER_HOME=$PWD/.gradle - rm -rf $GRADLE_USER_HOME/fdroid - mkdir -p $GRADLE_USER_HOME/fdroid - git ls-remote https://gitlab.com/fdroid/gradle-plugins.git master - curl --silent https://gitlab.com/fdroid/gradle-plugins/-/archive/master/gradle-plugins-master.tar.gz | tar -xz --directory=$GRADLE_USER_HOME/fdroid --strip-components=1 - git ls-remote https://gitlab.com/fdroid/issuebot.git master - curl --silent https://gitlab.com/fdroid/issuebot/-/archive/master/issuebot-master.tar.gz | tar -xz --strip-components=1 - python3 -m venv --system-site-packages --clear issuebot-env - . issuebot-env/bin/activate - ./issuebot.py # git_stats used to run here, redirect to new location - echo '' > public/index.html check_git_repos: image: debian:bookworm-slim stage: test needs: [] only: refs: - schedules variables: - $CHECK_GIT_REPO == "true" artifacts: when: on_failure expire_in: 1 month paths: - public script: - apt-get update - apt-get -qy install --no-install-recommends ca-certificates git python3-colorama python3-yaml - tools/check-git-repo-availability.py || export EXITVALUE=1 - chown -R $(whoami) . - test -d public || mkdir public - cp `git status | grep -Eo 'metadata/.*\.yml'` public/ || true - exit $EXITVALUE # checkupdates should only be allowed to modify app metadata files, # e.g. metadata/*.yml. Anything else should throw an error here. checkupdates_filter: image: debian:bookworm-slim stage: test needs: [] rules: - if: $CI_PROJECT_PATH == 'fdroid/checkupdates-bot-fdroiddata' script: - apt-get update - apt-get -qy install --no-install-recommends ca-certificates git - chown -R $(whoami) . - git fetch https://gitlab.com/fdroid/fdroiddata.git - | if git diff --merge-base FETCH_HEAD HEAD --name-only | grep -v '^metadata/\S*\.yml$'; then printf "\x1b[31mERROR Modifications to paths checkupdates-bot is not allowed to change! \x1b[0m\n" exit 1 else echo "All file modifications match paths checkupdates-bot is allowed to change." fi strip EXIF: stage: test needs: [] image: debian:bookworm-slim rules: - changes: paths: - "**.JPEG" - "**.JPEg" - "**.JPeG" - "**.JPeg" - "**.JpEG" - "**.JpEg" - "**.JpeG" - "**.Jpeg" - "**.jPEG" - "**.jPEg" - "**.jPeG" - "**.jPeg" - "**.jpEG" - "**.jpEg" - "**.jpeG" - "**.jpeg" - "**.JPG" - "**.JPg" - "**.JpG" - "**.Jpg" - "**.jPG" - "**.jPg" - "**.jpG" - "**.jpg" - "**.PNG" - "**.PNg" - "**.PnG" - "**.Png" - "**.pNG" - "**.pNg" - "**.pnG" - "**.png" script: - apt-get -qy update - apt-get -qy dist-upgrade - apt-get -qy install --no-install-recommends exiftool git - chown -R $(whoami) . # JPEG - find . -type f -iname "*.jpg" -o -iname "*.jpeg" -exec chmod a-x {} \; - find . -type f -iname "*.jpg" -o -iname "*.jpeg" -exec exiftool -quiet -all= {} \; # PNG - find . -type f -iname "*.png" -exec chmod a-x {} \; - find . -type f -iname "*.png" -exec exiftool -quiet -all= {} \; - git diff --exit-code --color || { printf "\x1b[31mERROR Image files (JPEG, PNG) should be stripped and not be set executable:\x1b[0m\n"; echo "Try using exiftool, jpegoptim, optipng (e.g. `exiftool -all= foo.png`)."; echo "For more info, see:"; echo "https://umbrella.cisco.com/blog/picture-perfect-how-jpg-exif-data-hides-malware"; echo "https://beinset.medium.com/exif-metadata-a-hidden-door-to-cyber-vulnerabilities-52b0dd2ff4de"; exit 1; } repo/index.html: stage: test needs: [] image: debian:bookworm-slim rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: paths: - .gitlab-ci.yml - repo/index.* # only check pushes to fdroids own repo - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_PATH == "fdroid/fdroiddata" changes: compare_to: 'refs/heads/master' paths: - .gitlab-ci.yml - repo/index.* script: - apt-get -qy update - apt-get -qy install --no-install-recommends linkchecker tidy - export EXITVALUE=0 - function set_error() { export EXITVALUE=1; printf "\x1b[31mERROR `history|tail -2|head -1|cut -b 6-500`\x1b[0m\n"; } # include fake APK for linkchecker - for f in $(grep -Eo '[^"]+/org\.fdroid\.fdroid_[0-9]+\.apk' repo/index.html | sort -u); do (cd repo; mkdir -p $(dirname $f); touch $f ${f}.asc); done - linkchecker repo/index.html || set_error - tidy repo/index.html || set_error - exit $EXITVALUE weblate merge conflict: stage: test needs: [] image: debian:bookworm-slim rules: - changes: - .gitlab-ci.yml - config/*/antiFeatures.yml - config/*/categories.yml - config/antiFeatures.yml - config/categories.yml script: - apt-get update - apt-get -qy upgrade - apt-get -qy install --no-install-recommends ca-certificates git - git config user.name "$CI_PIPELINE_ID/$CI_JOB_ID" - git config user.email $CI_PROJECT_PATH@f-droid.org - git fetch https://hosted.weblate.org/git/f-droid/anti-features/ - git checkout -B weblate FETCH_HEAD - export EXITVALUE=0 - if ! git rebase $CI_COMMIT_SHA; then export EXITVALUE=1; set -x; while git rebase --skip; do echo; done; set +x; fi - git diff --exit-code - exit $EXITVALUE PUBLISH: image: debian:bookworm-backports rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: paths: - .gitlab-ci.yml - config.yml - config/*.yml - config/*/*.yml # only check pushes to fdroids own repo - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_PATH == "fdroid/fdroiddata" changes: compare_to: 'refs/heads/master' paths: - .gitlab-ci.yml - config.yml - config/*.yml - config/*/*.yml script: - apt-get update - apt-get -qy upgrade - apt-get -qy install --no-install-recommends -t bookworm-backports androguard apksigner curl default-jdk-headless git gpg gpg-agent python3-asn1crypto python3-defusedxml python3-git python3-ruamel.yaml python3-yaml rsync - export fdroidserver=$CI_PROJECT_DIR/fdroidserver - export PATH=$fdroidserver:$PATH - *install_fdroid_server - mkdir -p $CI_PROJECT_DIR/archive - mkdir -p $CI_PROJECT_DIR/unsigned - *set_up_test_keystore - cp $fdroidserver/tests/urzip-release-unsigned.apk $CI_PROJECT_DIR/unsigned/info.guardianproject.urzip_100.apk - cp $fdroidserver/tests/metadata/info.guardianproject.urzip.yml $CI_PROJECT_DIR/metadata/ # run signpkg.sh - fdroid publish --verbose - fdroid gpgsign --verbose - rsync --progress repo/* $serverwebroot/ # run signindex.sh - fdroid gpgsign --verbose - fdroid signindex --verbose - rsync --stats repo/* $serverwebroot/