pmbootstrap CI

From postmarketOS Wiki

Continuous Integration (CI) scripts run whenever sending a patch into one of the source trees of postmarketOS. These run various tests, to make sure that new patches don't introduce new bugs and to do various style checks (linting) so the code looks consistent. The command pmbootstrap ci can be used to run the same CI scripts locally. If they fail, you can fix your patch and run them again - and once they pass you can be confident that they will also pass after you submitted them.


Change to the directory of the git repository of which you would like to run the CI scripts. Then run pmbootstrap ci for an interactive selection of which scripts to run.

$ cd ~/code/pmbootstrap
$ pmbootstrap ci
[20:42:49] Available CI scripts (4):
[20:42:49] * ruff: lint all python scripts
[20:42:49] * shellcheck: lint all shell scripts
[20:42:49] * vermin: verify that we don't use too new python features
[20:42:49] * pytest: run pmbootstrap python testsuite (slow)
[20:42:49] Which script? [all]:

It is also possible to directly start one or more scripts without the interactive prompt. Just add the name of the scripts you want to run at the end, for example:

$ pmbootstrap ci ruff vermin

See pmbootstrap ci -h for the full usage instructions.

Example CI script

Here is a minimal example for checking all shell scripts in the git repository with shellcheck.

Create the following file as .ci/

#!/bin/sh -e
# Description: lint all shell scripts

if [ "$(id -u)" = 0 ]; then
	set -x
	apk -q add shellcheck
	exec su "${TESTUSER:-build}" -c "sh -e $0"

find . -name '*.sh' |
while read -r file; do
	echo "shellcheck: $file"
	shellcheck "$file"

Make it executable:

$ chmod +x .ci/

If the git repository is on SourceHut, put the following in .build.yml:

image: alpine/latest
  - sudo
  - shellcheck: |
      cd name-of-your-git-repository
      sudo .ci/

If the git repository is on GitLab, put the following in .gitlab-ci.yml:

image: alpine:latest
    - adduser -D build
    - .ci/


CI script

The CI scripts need to have a specific format, so pmbootstrap ci can use them. They must be named .ci/$ in the root of the git repository.

The scripts must follow the following template:

#!/bin/sh -e
# Description: put your description here
# Options: slow

if [ "$(id -u)" = 0 ]; then
	set -x
	apk -q add \
	exec su "${TESTUSER:-build}" -c "sh -e $0"



The comments on the top hold some metadata:

  • # Description: this is what gets displayed in the interactive prompt
  • # Options: a list of options to configure how pmbootstrap should handle this script. Omit this if no options are used.
    • native: this script should run natively instead of running it inside a pmbootstrap chroot. This makes sense when the CI script needs to run pmbootstrap itself, as pmbootstrap can't run inside pmbootstrap. Note that the block that usually installs dependencies will get skipped when running this with pmbootstrap ci, so add checks for all dependencies before starting the tests and give the user a helpful message if something is missing. See .ci/ in pmbootstrap.git as example.
    • slow: this script takes significant time to execute. Usually there are a few linter scripts that run rather fast, and a testsuite that takes longer. pmbootstrap will run scripts with the slow option at the end.
  • # link to this wiki page, and a marker so pmbootstrap ci knows that it may use this script

Installing dependencies

The block at if [ "$(id -u)" = 0 ]; then is for installing dependencies with apk.

  • Add set -x at the beginning, so the apk and exec su commands are printed to the terminal, it's useful to know what packages are installed and when the other block is entered. The output is well readable with these two commands printed.
  • At the end of the block, execute the CI script again but as testuser. The install dependencies block will get skipped then.
  • When running on a CI server (e.g. SourceHut, GitLab), this block will run as root inside an Alpine edge container (even if the script has native set).
  • When running with pmbootstrap ci, this block will run as root user inside the pmbootstrap chroot. It has an apk cache set up (as always when using pmbootstrap's chroot). When this script has the native option set, pmbootstrap ci just skips this block and runs the part below it.
  • Make sure everything that is needed to run the CI script is packaged in Alpine, consider packaging it yourself otherwise.

Running the test

  • CI scripts runs with the git repository as working dir.
  • Everything after the "Installing dependencies" block runs as testuser. "pmos" in case of pmbootstrap, or whatever the CI server uses.
  • Make sure that its output is well readable, therefore it might be better to not use set -x and instead echo some messages about what it is doing

SourceHut config

For SourceHut, a build manifest needs to be written, typically into a .build.yml file in the root of the git repository. See the example above.

This file just calls the CI scripts inside an alpine image. It uses sudo to elevate rights to root, so we can install dependencies at the beginning of the CI scripts (and do it only there, not repeat it in the manifest).

In theory it would be nice to use doas instead of sudo, but as of writing that didn't work out of the box.

GitLab config

For GitLab, a .gitlab-ci.yml file needs to be written. See the example above.

See also

  • pmbootstrap and pmaports git repositories have plenty of CI scripts, all compatible with pmbootstrap ci
  • #2169: issue for implementing this feature