├── .editorconfig ├── .flake8 ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── COPYING ├── README.md ├── buildscript ├── config ├── arch │ ├── amd64.ini │ └── arm64.ini ├── branch │ ├── eos3.4.ini │ ├── eos3.5.ini │ ├── eos3.6.ini │ ├── eos3.7.ini │ ├── eos3.8.ini │ ├── eos3.9.ini │ └── master.ini ├── defaults.ini ├── local.ini.example ├── personality │ ├── base.ini │ ├── en.ini │ ├── es.ini │ ├── fr.ini │ └── pt_BR.ini ├── platform │ ├── pinebookpro.ini │ ├── rpi3.ini │ └── rpi4.ini ├── product-personality │ ├── eos-base.ini │ └── eos-en.ini ├── product │ ├── eos.ini │ ├── eoscustom.ini │ └── eosinstaller.ini └── schema.ini ├── data ├── arm64 │ └── boot.src ├── branding │ └── gnome-initial-setup │ │ └── es │ │ └── gnome-initial-setup.conf ├── keys │ └── eos-ostree-signing-key.asc ├── plymouth │ └── themes │ │ └── spinner │ │ ├── animation-0001.png │ │ ├── animation-0002.png │ │ ├── animation-0003.png │ │ ├── animation-0004.png │ │ ├── animation-0005.png │ │ ├── animation-0006.png │ │ ├── animation-0007.png │ │ ├── animation-0008.png │ │ ├── animation-0009.png │ │ ├── animation-0010.png │ │ ├── animation-0011.png │ │ ├── animation-0012.png │ │ ├── animation-0013.png │ │ ├── animation-0014.png │ │ ├── animation-0015.png │ │ ├── animation-0016.png │ │ ├── animation-0017.png │ │ ├── animation-0018.png │ │ ├── animation-0019.png │ │ ├── animation-0020.png │ │ ├── animation-0021.png │ │ ├── animation-0022.png │ │ ├── animation-0023.png │ │ ├── animation-0024.png │ │ ├── animation-0025.png │ │ ├── animation-0026.png │ │ ├── animation-0027.png │ │ ├── animation-0028.png │ │ ├── animation-0029.png │ │ ├── animation-0030.png │ │ ├── animation-0031.png │ │ ├── animation-0032.png │ │ ├── animation-0033.png │ │ ├── animation-0034.png │ │ ├── animation-0035.png │ │ ├── animation-0036.png │ │ ├── background-tile.png │ │ ├── box.png │ │ ├── bullet.png │ │ ├── entry.png │ │ ├── lock.png │ │ ├── spinner.plymouth │ │ ├── throbber-0001.png │ │ ├── throbber-0002.png │ │ ├── throbber-0003.png │ │ ├── throbber-0004.png │ │ ├── throbber-0005.png │ │ ├── throbber-0006.png │ │ ├── throbber-0007.png │ │ ├── throbber-0008.png │ │ ├── throbber-0009.png │ │ ├── throbber-0010.png │ │ ├── throbber-0011.png │ │ └── throbber-0012.png ├── rpi-common │ ├── LICENCE.broadcom │ ├── bootcode.bin │ ├── config.txt │ ├── fixup.dat │ ├── fixup4.dat │ ├── fixup4cd.dat │ ├── fixup4db.dat │ ├── fixup4x.dat │ ├── fixup_cd.dat │ ├── fixup_db.dat │ ├── start.elf │ ├── start4.elf │ ├── start4cd.elf │ ├── start4db.elf │ ├── start4x.elf │ ├── start_cd.elf │ └── start_db.elf ├── rpi4 │ ├── bcm2711-rpi-4-b.dtb │ └── bcm2711-rpi-400.dtb ├── s905x-bootloader │ ├── acs.bin │ ├── bl2.bin │ ├── bl21.bin │ ├── bl30.bin │ ├── bl301.bin │ └── bl31.img └── s912-bootloader │ ├── acs.bin │ ├── bl2.bin │ ├── bl21.bin │ ├── bl30.bin │ ├── bl301.bin │ └── bl31.img ├── eos-image-builder ├── helpers ├── assemble-manifest ├── create-grub-images ├── create-iso ├── create-vm-image ├── eib-chroot ├── fetch-remote-collection-id ├── generate-ovf-files ├── git-ssh ├── kill-chroot-procs ├── mutable-path └── seed-kolibri-channels ├── hooks ├── content │ ├── 50-flatpak │ ├── 50-gnome-software-cache │ └── 50-kalite ├── image │ ├── 50-branding-background │ ├── 50-branding-fbe │ ├── 50-firewall-localonly.chroot │ ├── 50-flatpak.chroot │ ├── 50-gnome-software-cache │ ├── 50-hostname │ ├── 50-initramfs-append │ ├── 50-kalite │ ├── 50-language │ ├── 50-locales │ ├── 50-metrics-urls │ ├── 50-reclaim-swap-stamp │ ├── 50-safe-defaults.chroot │ ├── 50-timezone │ ├── 50-update-done-stamp │ ├── 50-update-server.chroot │ ├── 50-updater-p2p-student.chroot │ ├── 50-updater-p2p-teacher.chroot │ ├── 50-xkb-layout.chroot │ ├── 53-ek-content-preload │ ├── 54-ek-launchers │ ├── 60-chromium-policies │ ├── 60-dconf-prepare │ ├── 60-flatpak-autoinstall-counters.chroot │ ├── 60-kolibri-content │ ├── 61-dconf-compile.chroot │ ├── 62-kolibri-automatic-provision │ ├── 62-kolibri-options │ ├── 63-icon-grid │ ├── 63-kolibri-chown.chroot │ ├── 70-flatpak-appstream-catalog │ ├── 70-flatpak-manifest │ ├── 70-ostree-manifest │ ├── 70-packages-manifest │ └── 80-ldconfig-aux-cache.chroot └── publish │ ├── 40-build-log │ ├── 42-sha256sums │ ├── 45-publish-s3 │ └── 50-publish ├── lib ├── applist.py ├── eib.py ├── eib.sh ├── eibflatpak.py ├── eibkolibri.py └── eibostree.py ├── pytest.ini ├── requirements-test.txt ├── run-build ├── stages ├── eib_error ├── eib_image ├── eib_manifest ├── eib_ostree └── eib_publish └── tests ├── __init__.py ├── conftest.py ├── data ├── flatpak │ ├── app-1-locale │ │ ├── files │ │ │ ├── en │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── en │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── app-1.mo │ │ │ ├── es │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── es │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── app-1.mo │ │ │ └── fr │ │ │ │ └── share │ │ │ │ └── locale │ │ │ │ └── fr │ │ │ │ └── LC_MESSAGES │ │ │ │ └── app-1.mo │ │ └── metadata │ ├── app-1 │ │ └── metadata │ ├── app-2-locale │ │ ├── files │ │ │ ├── en │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── en │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── app-2.mo │ │ │ ├── es │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── es │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── app-2.mo │ │ │ └── fr │ │ │ │ └── share │ │ │ │ └── locale │ │ │ │ └── fr │ │ │ │ └── LC_MESSAGES │ │ │ │ └── app-2.mo │ │ └── metadata │ ├── app-2 │ │ └── metadata │ ├── app-extra-data │ │ └── metadata │ ├── platform-1-locale │ │ ├── files │ │ │ ├── en │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── en │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── platform-1.mo │ │ │ ├── es │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── es │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── platform-1.mo │ │ │ └── fr │ │ │ │ └── share │ │ │ │ └── locale │ │ │ │ └── fr │ │ │ │ └── LC_MESSAGES │ │ │ │ └── platform-1.mo │ │ └── metadata │ ├── platform-1 │ │ └── metadata │ ├── platform-2-locale │ │ ├── files │ │ │ ├── en │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── en │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── platform-2.mo │ │ │ ├── es │ │ │ │ └── share │ │ │ │ │ └── locale │ │ │ │ │ └── es │ │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── platform-2.mo │ │ │ └── fr │ │ │ │ └── share │ │ │ │ └── locale │ │ │ │ └── fr │ │ │ │ └── LC_MESSAGES │ │ │ │ └── platform-2.mo │ │ └── metadata │ └── platform-2 │ │ └── metadata ├── test1.asc ├── test1.key ├── test2.asc ├── test2.key ├── test3.asc └── test3.key ├── eib ├── __init__.py ├── test_eibflatpak.py ├── test_eibkolibri.py ├── test_eibostree.py ├── test_image_config_parser.py └── test_ostree_trusted_keys.py ├── test_eib_sh.py ├── test_image_builder.py └── util.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file. 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | 10 | # Bash - Most helpers and hooks are bash, so wildcard those first and 11 | # override where needed below. 12 | [{*.sh,buildscript,stages/*,helpers/*,hooks/*/*}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | # Python 17 | [{*.py,eos-image-builder,run-build,helpers/{assemble-manifest,fetch-remote-collection-id,generate-ovf-files,kill-chroot-procs,kolibri-pick-content-from-channel,mutable-path,seed-kolibri-channels},hooks/{content/50-flatpak,image/{50-flatpak.chroot,52-ek-content-cache,62-kolibri-options,70-flatpak-appstream-catalog,70-flatpak-manifest,70-ostree-manifest,70-packages-manifest}}}] 18 | indent_size = 4 19 | max_line_length = 88 20 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # Allow slightly longer lines 3 | max-line-length = 88 4 | 5 | # Check python scripts without .py suffix 6 | filename = 7 | *.py, 8 | ./eos-image-builder, 9 | ./helpers/assemble-manifest, 10 | ./helpers/fetch-remote-collection-id, 11 | ./helpers/generate-ovf-files, 12 | ./helpers/kill-chroot-procs, 13 | ./helpers/kolibri-pick-content-from-channel, 14 | ./helpers/mutable-path, 15 | ./helpers/seed-kolibri-channels, 16 | ./hooks/content/50-flatpak, 17 | ./hooks/image/50-flatpak.chroot, 18 | ./hooks/image/62-kolibri-options, 19 | ./hooks/image/70-flatpak-appstream-catalog, 20 | ./hooks/image/70-flatpak-manifest, 21 | ./hooks/image/70-ostree-manifest, 22 | ./hooks/image/70-packages-manifest, 23 | ./run-build 24 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # The tests are currently only available on master, so only run when 3 | # pushing to master or pull requests targeting master. 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | tests: 12 | strategy: 13 | # Let other configurations continue if one fails. 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-22.04 18 | - ubuntu-24.04 19 | runs-on: ${{ matrix.os }} 20 | name: Run tests 21 | steps: 22 | - name: System dependencies 23 | run: | 24 | sudo apt-get update 25 | sudo apt-get install -y \ 26 | flatpak \ 27 | gir1.2-glib-2.0 \ 28 | gir1.2-flatpak-1.0 \ 29 | gir1.2-ostree-1.0 \ 30 | gpg \ 31 | gpgv \ 32 | ostree \ 33 | python3-gi \ 34 | python3-pip 35 | - name: Checkout 36 | uses: actions/checkout@v2 37 | - name: Python dependencies 38 | run: | 39 | python3 -m pip install -r requirements-test.txt 40 | - name: Lint 41 | run: | 42 | python3 -m flake8 43 | - name: Tests 44 | run: | 45 | python3 -m pytest 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle 2 | *.deb 3 | *.log 4 | *.swp 5 | *~ 6 | *.pyc 7 | __pycache__ 8 | 9 | # Don't include local config overrides 10 | config/local.ini 11 | config/private.ini 12 | -------------------------------------------------------------------------------- /buildscript: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | # Run the eib_error stage on errors. Return code 2 is used for image 4 | # build failures to differentiate from locking failures. 5 | stage_error() { 6 | eib_error 7 | exit 2 8 | } 9 | trap stage_error ERR 10 | 11 | # Ensure basic settings are available from the environment. 12 | for var in EIB_BASELIB EIB_SRCDIR EIB_CACHEDIR EIB_DATADIR \ 13 | EIB_HELPERSDIR EIB_OUTDIR EIB_TMPDIR EIB_CONTENTDIR 14 | do 15 | if [ ! -v $var ]; then 16 | echo "error: required variable $var not set" >&2 17 | exit 1 18 | fi 19 | done 20 | 21 | # Change back to the source directory since we've chrooted. 22 | cd "${EIB_SRCDIR}" 23 | 24 | # Declare this as a safe git directory inside the build root since this 25 | # is running as root and the repo may be owned by a different user. 26 | git config --system --add safe.directory "${EIB_SRCDIR}" 27 | 28 | # Make sure to find our stage scripts and python library 29 | export PATH="${EIB_SRCDIR}/stages:${PATH}" 30 | export PYTHONPATH="${EIB_SRCDIR}/lib${PYTHONPATH:+:$PYTHONPATH}" 31 | 32 | # If the localdir has a lib directory, prepend that to the python path 33 | if [ -n "${EIB_LOCALDIR}" ] && [ -d "${EIB_LOCALDIR}/lib" ]; then 34 | PYTHONPATH="${EIB_LOCALDIR}/lib:${PYTHONPATH}" 35 | fi 36 | 37 | # Use our helper script for git access over ssh 38 | export GIT_SSH="${EIB_HELPERSDIR}/git-ssh" 39 | 40 | # Tell aws tools to use credentials in sysconfdir 41 | export AWS_SHARED_CREDENTIALS_FILE="${EIB_SYSCONFDIR}/aws-credentials" 42 | 43 | . "${EIB_BASELIB}" 44 | 45 | exec > >(tee "${EIB_TMPDIR}"/build.txt) 46 | exec 2>&1 47 | 48 | # Real build stages 49 | eib_ostree 50 | eib_image 51 | eib_manifest 52 | eib_publish 53 | -------------------------------------------------------------------------------- /config/arch/amd64.ini: -------------------------------------------------------------------------------- 1 | # amd64 architecture settings 2 | 3 | [buildroot] 4 | # Additional packages for x86; needed to build boot.zip, .iso images 5 | # and VM images. 6 | packages_add = 7 | dosfstools 8 | genisoimage 9 | grub-common 10 | grub2 11 | mtools 12 | squashfs-tools 13 | xorriso 14 | zip 15 | -------------------------------------------------------------------------------- /config/arch/arm64.ini: -------------------------------------------------------------------------------- 1 | [flatpak-remote-eos-runtimes] 2 | # This remote is only for legacy runtimes not available on arm64 3 | enable = false 4 | 5 | [image] 6 | # No boot.zip (installable bootloader) or ISO support for ARM 7 | boot_zip = false 8 | iso = false 9 | 10 | # Temporarily drop finance and resume from arm64 images 11 | # https://phabricator.endlessm.com/T33876#951709 12 | [flatpak-remote-eos-apps] 13 | apps_del = 14 | com.endlessm.finance 15 | com.endlessm.resume 16 | 17 | [flatpak-remote-flathub] 18 | apps_del = 19 | cc.arduino.IDE2 20 | com.endlessnetwork.aqueducts 21 | com.endlessnetwork.blendertutorials 22 | com.endlessnetwork.dragonsapprentice 23 | com.endlessnetwork.fablemaker 24 | com.endlessnetwork.frogsquash 25 | com.endlessnetwork.MidnightmareTeddy 26 | com.endlessnetwork.missilemath 27 | com.endlessnetwork.passage 28 | com.endlessnetwork.tankwarriors 29 | com.endlessnetwork.whitehouse 30 | com.orama_interactive.Pixelorama 31 | org.blender.Blender 32 | -------------------------------------------------------------------------------- /config/branch/eos3.4.ini: -------------------------------------------------------------------------------- 1 | # eos3.4 branch settings 2 | 3 | [ostree] 4 | # A different deploy ref is needed for eos3.4+ so that users are 5 | # required to get a specific eos-updater on the eos3.3 branch. 6 | # 7 | # https://phabricator.endlessm.com/T22056 8 | stable_branch = eos3a 9 | -------------------------------------------------------------------------------- /config/branch/eos3.5.ini: -------------------------------------------------------------------------------- 1 | # eos3.5 branch settings 2 | 3 | [ostree] 4 | # A different deploy ref is needed for eos3.4+ so that users are 5 | # required to get a specific eos-updater on the eos3.3 branch. 6 | # 7 | # https://phabricator.endlessm.com/T22056 8 | stable_branch = eos3a 9 | -------------------------------------------------------------------------------- /config/branch/eos3.6.ini: -------------------------------------------------------------------------------- 1 | # eos3.6 branch settings 2 | 3 | [ostree] 4 | # A different deploy ref is needed for eos3.4+ so that users are 5 | # required to get a specific eos-updater on the eos3.3 branch. 6 | # 7 | # https://phabricator.endlessm.com/T22056 8 | stable_branch = eos3a 9 | -------------------------------------------------------------------------------- /config/branch/eos3.7.ini: -------------------------------------------------------------------------------- 1 | # eos3.7 branch settings 2 | 3 | [ostree] 4 | # A different deploy ref is needed for eos3.4+ so that users are 5 | # required to get a specific eos-updater on the eos3.3 branch. 6 | # 7 | # https://phabricator.endlessm.com/T22056 8 | stable_branch = eos3a 9 | -------------------------------------------------------------------------------- /config/branch/eos3.8.ini: -------------------------------------------------------------------------------- 1 | # eos3.8 branch settings 2 | 3 | [ostree] 4 | # A different deploy ref is needed for eos3.4+ so that users are 5 | # required to get a specific eos-updater on the eos3.3 branch. 6 | # 7 | # https://phabricator.endlessm.com/T22056 8 | stable_branch = eos3a 9 | -------------------------------------------------------------------------------- /config/branch/eos3.9.ini: -------------------------------------------------------------------------------- 1 | # eos3.9 branch settings 2 | 3 | [ostree] 4 | # A different deploy ref is needed for eos3.4+ so that users are 5 | # required to get a specific eos-updater on the eos3.3 branch. 6 | # 7 | # https://phabricator.endlessm.com/T22056 8 | stable_branch = eos3a 9 | -------------------------------------------------------------------------------- /config/branch/master.ini: -------------------------------------------------------------------------------- 1 | # Settings for master branch builds 2 | 3 | [ostree] 4 | # Use the dev repo for deployment since master is never released 5 | deploy_url = ${dev_deploy_repo_url}/${repo} 6 | 7 | # Master images should always follow the master ostree. 8 | stable_branch = ${build:branch} 9 | 10 | # Enable the Flathub beta repo. 11 | [flatpak-remote-flathub-beta] 12 | enable = true 13 | -------------------------------------------------------------------------------- /config/local.ini.example: -------------------------------------------------------------------------------- 1 | # Example local configuration. When installed as local.ini, it can be 2 | # used to override any other settings for the configured image variant. 3 | # This is primarily useful when testing changes to the image builder or 4 | # producing a one time custom image. 5 | # 6 | # Below are some settings that are typically useful in this case, but 7 | # all options are available to be changed. See defaults.ini for 8 | # descriptions of the options. 9 | 10 | [image] 11 | # Skip GPG signing of the completed image files since the default key ID 12 | # isn't available. Alternatively, the key ID for any private key in the 13 | # GPG homedir at /etc/eos-image-builder/gnupg. 14 | signing_keyid = 15 | 16 | # Most people probably do not need VM images when building locally. Disabling 17 | # by default to save storage and time. 18 | vm_image = false 19 | 20 | # Some products are compressed with xz, trading the one-time cost of 21 | # substantially slower image builds for substantially smaller image 22 | # files. When testing locally, you probably don't care how big the image 23 | # file is, but you'd probably like it to build quickly. 24 | compression = gz 25 | 26 | # One may want to completely disable flatpaks for local builds to save storage 27 | # and time. Also, disabling flatpak support may increase the changes of a 28 | # successful build. 29 | #[flatpak] 30 | #enable = false 31 | 32 | [flatpak-remote-eos-apps] 33 | # Completely override the set of installed EOS apps for testing. This will 34 | # take precedence over all other image variant configuration. This can 35 | # be useful if the exact app set is known or if the build is for testing 36 | # and the full multi-gigabyte app set is not needed. 37 | apps = 38 | com.endlessm.resume 39 | 40 | # Alternatively, a set of apps can be added or deleted from the existing 41 | # configuration by using the _add_/_del_ merged settings. The above 42 | # install option would need to be commented out for these to take 43 | # effect. 44 | #apps_add = org.my.App 45 | #apps_del = com.endlessm.resume 46 | 47 | [flatpak-remote-flathub] 48 | # Completely override the set of installed Flathub apps. See documentation 49 | # for eos-apps above. 50 | apps = 51 | com.endlessm.photos 52 | 53 | [buildroot] 54 | # The buildroot is generated using packages on the internal OBS repos, 55 | # which requires VPN access. This is not implemented yet, but running 56 | # builds outside the SF office would be better to use a public package 57 | # mirror. 58 | #repo = http://non-existent-mirror.endlessm.com/eos 59 | -------------------------------------------------------------------------------- /config/personality/base.ini: -------------------------------------------------------------------------------- 1 | # base personality settings 2 | 3 | [content] 4 | 5 | [image] 6 | kalite_content = false 7 | 8 | [flatpak-remote-eos-apps] 9 | # Delete all default additions and explicitly build the list from scratch. 10 | apps_del = ${apps_add_defaults} 11 | 12 | [flatpak-remote-flathub] 13 | # Delete all default additions and explicitly build the list from scratch. 14 | apps_del = ${apps_add_defaults} 15 | apps_add = 16 | org.endlessos.Key 17 | -------------------------------------------------------------------------------- /config/personality/en.ini: -------------------------------------------------------------------------------- 1 | # en personality settings 2 | 3 | [image] 4 | language = en_US.utf8 5 | timezone = America/Los_Angeles 6 | 7 | # Global specific apps. See defaults.ini for default set. 8 | [flatpak-remote-eos-apps] 9 | apps_add = 10 | com.endlessm.animals.en 11 | com.endlessm.biology.en 12 | com.endlessm.cooking.en 13 | com.endlessm.encyclopedia.en 14 | com.endlessm.health.en 15 | com.endlessm.healthy_teeth.en 16 | com.endlessm.howto.en 17 | com.endlessm.math.en 18 | com.endlessm.myths.en 19 | com.endlessm.travel.en 20 | com.endlessm.water_and_sanitation.en 21 | com.endlessm.wiki_art.en 22 | com.endlessnetwork.arduinoprojects 23 | com.endlessnetwork.blendertutorials 24 | com.endlessnetwork.csstutorials 25 | com.endlessnetwork.drawingtutorials 26 | com.endlessnetwork.htmltutorials 27 | com.endlessnetwork.sciencesnacks 28 | 29 | [endlesskey] 30 | collections_add = 31 | artist 32 | athlete 33 | curious 34 | explorer 35 | extras 36 | inventor 37 | scientist 38 | -------------------------------------------------------------------------------- /config/personality/es.ini: -------------------------------------------------------------------------------- 1 | # es personality settings 2 | 3 | [image] 4 | language = es_MX.utf8 5 | timezone = America/Mexico_City 6 | 7 | # Brand-specific configuration and assets for the First Boot Experience 8 | branding_fbe_config = ${build:datadir}/branding/gnome-initial-setup/es/gnome-initial-setup.conf 9 | 10 | [flatpak] 11 | locales = es 12 | 13 | # Spanish specific apps. See defaults.ini for default set. 14 | [flatpak-remote-eos-apps] 15 | apps_add = 16 | com.endlessm.animals.es 17 | com.endlessm.astronomy.es 18 | com.endlessm.biology.es 19 | com.endlessm.celebrities.es 20 | com.endlessm.childrens_collection.es 21 | com.endlessm.cooking.es 22 | com.endlessm.creativity_center.es 23 | com.endlessm.dinosaurs.es 24 | com.endlessm.disabilities.es 25 | com.endlessm.encyclopedia.es 26 | com.endlessm.farming.es_GT 27 | com.endlessm.geography.es 28 | com.endlessm.handicrafts.es 29 | com.endlessm.health.es 30 | com.endlessm.healthy_teeth.es 31 | com.endlessm.history.es 32 | com.endlessm.howto.es 33 | com.endlessm.maternity.es 34 | com.endlessm.math.es 35 | com.endlessm.microenterprises.es_GT 36 | com.endlessm.myths.es 37 | com.endlessm.physics.es 38 | com.endlessm.programming 39 | com.endlessm.programming_guide.es 40 | com.endlessm.soccer.es 41 | com.endlessm.social_enterprises.es_GT 42 | com.endlessm.socialsciences.es 43 | com.endlessm.travel.es 44 | com.endlessm.video_animal_kingdom 45 | com.endlessm.vroom.es 46 | com.endlessm.water_and_sanitation.es 47 | com.endlessm.world_literature.es 48 | com.endlessnetwork.arduinoprojects 49 | com.endlessnetwork.blendertutorials 50 | com.endlessnetwork.csstutorials 51 | com.endlessnetwork.htmltutorials 52 | com.endlessnetwork.sciencesnacks 53 | 54 | [flatpak-remote-flathub] 55 | apps_add = 56 | ar.com.pilas_engine.App 57 | as.may.moat 58 | com.github.carlos157oliveira.Calculus 59 | com.mardojai.DiccionarioLengua 60 | net.ankiweb.Anki 61 | org.eclipse.Java 62 | org.freecad.FreeCAD 63 | org.gnome.gbrainy 64 | org.kde.kalgebra 65 | org.kde.kblocks 66 | org.kde.kbruch 67 | org.kde.kgeography 68 | org.kde.khangman 69 | org.kde.kstars 70 | org.learningequality.Kolibri 71 | org.moneymanagerex.MMEX 72 | org.qgis.qgis 73 | org.stellarium.Stellarium 74 | org.sugarlabs.TypingTurtle 75 | 76 | [endlesskey] 77 | collections_add = 78 | spanish 79 | spanish-extras 80 | 81 | [kolibri] 82 | automatic_provision = true 83 | install_channels = 84 | # Simulaciones interactivas PhET [533] 85 | 8fa678af1dd05329bf3218c549b84996 86 | # Sikana (Español) [40] 87 | 30c71c99c42c57d181e8aeafd2e15e5f 88 | # Ciencia NASA [12] 89 | da53f90b1be25752a04682bbc353659f 90 | # Khan Academy (Español) [87] 91 | c1f2b7e6ac9f56a2bb44fa7a48b66dce 92 | 93 | [kolibri-30c71c99c42c57d181e8aeafd2e15e5f] 94 | include_node_ids = 95 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Botiquín de emergencia [video] 96 | 38dc0038fdad501da670ed71274b4bb1 97 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Botiquín de emergencia [document] 98 | c28baeb64adb54309d8e1e53443b5769 99 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Cómo llamar a los servicios de emergencia [video] 100 | 529a3ae25f9e5f4293490e2630c3e4f6 101 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Cómo llamar a los servicios de emergencia [document] 102 | e44df7ef6e525003ae287b8a8291ccc2 103 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Cómo prevenir los accidentes de la vida cotidiana [video] 104 | 8c38aa3d48ce5a88831d992bb6ab3bdc 105 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Cómo prevenir los accidentes de la vida cotidiana [document] 106 | 7b98a4aca8d05ff18f23cdff76d8b77d 107 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Primeros auxilios : Posición lateral de seguridad [video] 108 | 24d55c34a0735bb2ae6c1eb296cd00d3 109 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Primeros auxilios : Posición lateral de seguridad [document] 110 | 23edc5ba956b5b6d9d3ae7448bbad13d 111 | # Salud / Aprende a salvar una vida / Conocimientos básicos / Primeros auxilios : Cómo utilizar un desfibrilador [document] 112 | dda8b214bec255a398dde4f8fee7860b 113 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Crisis convulsiva [video] 114 | 2e5bdf6523a25cc2a6e2e4dce9351bb4 115 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Crisis convulsiva [document] 116 | d82c410639405cd6a06daf1b7a9ca546 117 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Traumatismo craneal y de la columna vertebral [video] 118 | 45996c6efde05679afb74ae1dded3805 119 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Traumatismo craneal y de la columna vertebral [document] 120 | ef63e295aa2f5035a863f62d78875181 121 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios: Herida leve [video] 122 | 123fe0f584b95e508e14b80bda2377f7 123 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios: Herida leve [document] 124 | 5a63b904389458b8b366c2955e4b2477 125 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Herida grave [document] 126 | 95e43908341052a18d291687ea2312a3 127 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Hematoma [video] 128 | 3527fa87085a5688afd8a1424076a13d 129 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Hematoma [document] 130 | 72044a552a1f5b4d97923ec129532533 131 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Picadura de garrapata [video] 132 | 0584f5dc75e35c148a525552aaee7c17 133 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Picadura de garrapata [document] 134 | f525e8bf88da526b94aecc782756ce23 135 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Mordedura de serpiente [video] 136 | 96322b64272b53cead7bfdd186c6d70a 137 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Mordedura de serpiente [document] 138 | 89e5ba33db33503b9cc81c52108e4ee6 139 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Hemorragia [video] 140 | c52a6e4fa9385866b6e42c3c54fc708d 141 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Hemorragia [document] 142 | e36f43e22218534f830bfc6d6354ef3c 143 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Sangrado de nariz [video] 144 | bb170b868bb754ca906ab5a1bd027226 145 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Sangrado de nariz [document] 146 | a73da2996b3a54968d0d341b7bf22a90 147 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Miembro seccionado [video] 148 | bb252814a945503dbc424791220182c2 149 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Miembro seccionado [document] 150 | e6244ea7d92253e7adc6d7c28a58c69d 151 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Intoxicación de monóxido de carbono [video] 152 | 91f86dc917ea51979049ee185c9b361c 153 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Intoxicación de monóxido de carbono [document] 154 | 18d04bb98f48504a9b8f58131879792f 155 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Intoxicación alimentaria [video] 156 | 07d9bb78c5b75c12b5f19049b5a947b6 157 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Intoxicación alimentaria [document] 158 | 8bd0b20ac51a5d64b34cb1671c2708a4 159 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Intoxicación química [video] 160 | 48e6fa3e6db45ccda90b12985062eea4 161 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Intoxicación química [document] 162 | 4405598934f05b31881bb99d98c4a7d2 163 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Atragantamiento de un niño [video] 164 | d0bae51d4302578886839cb0eeff975f 165 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Atragantamiento de un niño [document] 166 | 3d75c5abf63a5054b697c3304bc26344 167 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Crisis diabética y desmayo [video] 168 | e4658d7ce7145ef6908091cb0b9f280d 169 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Crisis diabética y desmayo [document] 170 | 58caee429a855d77a140ad8e8eaf15b3 171 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Picadura de medusa [video] 172 | a01ede93e4485f9a8cc78e0da34f742e 173 | # Salud / Aprende a salvar una vida / Aprende las técnicas de primeros auxilios / Primeros auxilios : Picadura de medusa [document] 174 | cfa73dbf33d956298e0d8a47db0e6979 175 | -------------------------------------------------------------------------------- /config/personality/fr.ini: -------------------------------------------------------------------------------- 1 | # fr personality settings 2 | 3 | [image] 4 | language = fr_FR.utf8 5 | timezone = Europe/Paris 6 | 7 | [flatpak] 8 | locales = fr 9 | 10 | # French specific apps. See defaults.ini for default set. 11 | [flatpak-remote-eos-apps] 12 | apps_add = 13 | com.endlessm.encyclopedia.fr 14 | -------------------------------------------------------------------------------- /config/personality/pt_BR.ini: -------------------------------------------------------------------------------- 1 | # pt_BR personality settings 2 | 3 | [image] 4 | language = pt_BR.utf8 5 | timezone = America/Sao_Paulo 6 | 7 | [flatpak] 8 | locales = pt 9 | 10 | # Brazil specific apps. See defaults.ini for default set. 11 | [flatpak-remote-eos-apps] 12 | apps_add = 13 | com.endlessm.animals.pt 14 | com.endlessm.astronomy.pt 15 | com.endlessm.biology.pt 16 | com.endlessm.celebrities.pt 17 | com.endlessm.cooking.pt 18 | com.endlessm.dinosaurs.pt 19 | com.endlessm.encyclopedia.pt 20 | com.endlessm.farming.pt 21 | com.endlessm.geography.pt 22 | com.endlessm.history.pt 23 | com.endlessm.howto.pt 24 | com.endlessm.library.pt 25 | com.endlessm.math.pt 26 | com.endlessm.myths.pt 27 | com.endlessm.physics.pt 28 | com.endlessm.soccer.pt 29 | com.endlessm.socialsciences.pt 30 | com.endlessm.travel.pt 31 | com.endlessm.your_health.pt_BR 32 | -------------------------------------------------------------------------------- /config/platform/pinebookpro.ini: -------------------------------------------------------------------------------- 1 | [buildroot] 2 | # u-boot-tools needed for mkimage (boot script compilation) 3 | packages_add = u-boot-tools 4 | 5 | [image] 6 | # RK3399 only understands MBR partition tables 7 | partition_table = dos 8 | 9 | [ostree] 10 | # PINEBOOK Pro chooses the generic arm64 ostree 11 | platform = arm64 12 | -------------------------------------------------------------------------------- /config/platform/rpi3.ini: -------------------------------------------------------------------------------- 1 | [buildroot] 2 | # RPi3 boot path (via GPU) requires VFAT boot partition 3 | # u-boot-tools needed for mkimage (boot script compilation) 4 | packages_add = 5 | dosfstools 6 | u-boot-tools 7 | 8 | [image] 9 | # RPi3 boot path (via GPU) only understands MBR partition tables 10 | partition_table = dos 11 | 12 | [ostree] 13 | # RPi3 chooses the generic arm64 ostree 14 | platform = arm64 15 | -------------------------------------------------------------------------------- /config/platform/rpi4.ini: -------------------------------------------------------------------------------- 1 | [buildroot] 2 | # RPi4 boot path (via GPU) requires VFAT boot partition 3 | # u-boot-tools needed for mkimage (boot script compilation) 4 | packages_add = 5 | dosfstools 6 | u-boot-tools 7 | 8 | [image] 9 | # RPi4 boot path (via GPU) only understands MBR partition tables 10 | partition_table = dos 11 | 12 | [ostree] 13 | # RPi4 chooses the generic arm64 ostree 14 | platform = arm64 15 | -------------------------------------------------------------------------------- /config/product-personality/eos-base.ini: -------------------------------------------------------------------------------- 1 | [image] 2 | # on the basis someone downloading eos base is more sensitive to 3 | # bandwidth used than functionality/experience, compress this one 4 | # harder, but for other personalities, leave it to gz as most of 5 | # the additional data is already compressed, so we're trading 6 | # a lot of compress/install/live boot time for a very small 7 | # relative saving in download size 8 | compression = xz 9 | 10 | # Build VM image 11 | # This setting only works for arch=amd64, for all other values this is a no-op 12 | vm_image = true 13 | 14 | # Build QEMU qcow2 image 15 | qcow2 = true 16 | 17 | [iso] 18 | # The capacity of a single-later DVD+R, which is the smallest 19 | # commonly-available writeable format according to 20 | # https://en.wikipedia.org/wiki/DVD#Capacity, is 4,700,372,992 bytes. 21 | # If the base image is larger than this, something has gone horribly wrong! 22 | max_size = 4700000000 23 | -------------------------------------------------------------------------------- /config/product-personality/eos-en.ini: -------------------------------------------------------------------------------- 1 | [image] 2 | # Build VM image 3 | # This setting only works for arch=amd64, for all other values this is a no-op 4 | vm_image = true 5 | -------------------------------------------------------------------------------- /config/product/eos.ini: -------------------------------------------------------------------------------- 1 | # Settings for the eos product 2 | 3 | [image] 4 | # Build ISOs 5 | iso = true 6 | -------------------------------------------------------------------------------- /config/product/eoscustom.ini: -------------------------------------------------------------------------------- 1 | # Settings for the eoscustom product 2 | 3 | [ostree] 4 | # Use the standard eos ostree 5 | product = eos 6 | -------------------------------------------------------------------------------- /config/product/eosinstaller.ini: -------------------------------------------------------------------------------- 1 | # Settings for the eosinstaller product 2 | 3 | [image] 4 | # we xzip compress the small images like eos/base and eosinstaller 5 | # where people may be sensitive to download size, and the CPU time 6 | # spent on xz compression has a material relative impact on the 7 | # image size 8 | compression = xz 9 | 10 | # No auto resizing/partitioning 11 | rootfs_resize = false 12 | 13 | # No boot.zip for this, it makes no sense to dual-boot an installer-only image 14 | boot_zip = false 15 | 16 | [flatpak] 17 | # No apps on the installer regardless of personality 18 | enable = false 19 | -------------------------------------------------------------------------------- /config/schema.ini: -------------------------------------------------------------------------------- 1 | # Configuration validation for Endless image builder 2 | 3 | # Sections and key names match the config files, with suffixes as follows: 4 | # _required = true means that the key must be set 5 | # _values means the value, if set, must be within the space-separated list of 6 | # values here 7 | # _type = path means the value, if set, must be the path to a file which exists 8 | # _type = paths means the value, if set, must be a space-separated list of 9 | # path to files which exist 10 | 11 | [image] 12 | compression_required = true 13 | compression_values = gz 14 | xz 15 | partition_table_values = gpt 16 | dos 17 | branding_fbe_config_type = path 18 | icon_grid_type = paths 19 | initramfs_plymouth_watermark = path 20 | chromium_policies_managed = path 21 | chromium_policies_recommended = path 22 | settings = paths 23 | settings_locks = paths 24 | -------------------------------------------------------------------------------- /data/arm64/boot.src: -------------------------------------------------------------------------------- 1 | echo "Load u-Boot environment ..." 2 | ext4load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} /boot/uEnv.txt 3 | env import -t ${scriptaddr} ${filesize} 4 | 5 | echo "Load device tree ..." 6 | ext4load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /boot/${fdtdir}/${fdtfile} 7 | 8 | echo "Load kernel and unzip it ..." 9 | kernel_load_addr_r=${ramdisk_addr_r} 10 | ext4load ${devtype} ${devnum}:${distro_bootpart} ${kernel_load_addr_r} /boot/${kernel_image} 11 | unzip ${kernel_load_addr_r} ${kernel_addr_r} 12 | 13 | echo "Load RAM disk ..." 14 | ext4load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /boot/${ramdisk_image} 15 | 16 | echo "Boot ..." 17 | setenv bootargs root=LABEL=ostree ${bootargs} ${platform_bootargs} 18 | booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r} 19 | -------------------------------------------------------------------------------- /data/branding/gnome-initial-setup/es/gnome-initial-setup.conf: -------------------------------------------------------------------------------- 1 | [pages] 2 | run_welcome_tour=false 3 | 4 | [Language] 5 | initial_languages=es_AR.UTF-8;es_BO.UTF-8;es_CL.UTF-8;es_CO.UTF-8;es_CR.UTF-8;es_DO.UTF-8;es_EC.UTF-8;es_ES.UTF-8;es_GT.UTF-8;es_HN.UTF-8;es_MX.UTF-8;es_NI.UTF-8;es_PA.UTF-8;es_PE.UTF-8;es_PR.UTF-8;es_PY.UTF-8;es_SV.UTF-8;es_UY.UTF-8;es_VE.UTF-8 6 | -------------------------------------------------------------------------------- /data/keys/eos-ostree-signing-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBFNzuygBEADeDqJZ3xU2SNfjCiSVcM2NT5G+7tieRtq4gZA0qfEww3J668Bf 4 | 2nUaWgQHMEoShaNbfpjANhv41mGF+rJ/yR8ZANmx80n6EvYm9NCtOAhc4O3i3elZ 5 | f26qaEgWUmNZzemqgM2K/rK0aWOEHd8aOW4SrNlW0BIqciUWHPxOUzBbBfUsBxjp 6 | /Xukd9qzi3koHEzvZ7/gWqLIPaaIl3V0owhmcjnbbciJlO7MZbGjaNjrsObxHVtD 7 | KzaB5fLyyN7POZbXL5p1NCPmrqj5U2XY+yW5S6ryQJfkVJ5w0Nv4sLz+R28r1STl 8 | kBjU/is5nN7yAg/5sofQjnmih5Nl4ZKOoKZ8ZShMTmc1aJfSZNRqOPACnJOqc1Bt 9 | kADm6YgB9xoPZ7T+dL612hwsxrRHCCCzoGhLXl8S5/iPkBMe2MPBeYxkq5r3WMzq 10 | uavyFLHTHtQcJmK2bFaD5cHv2MQBKw+oZo80mk0447chBkAWolxFoPSlOE5h0KV8 11 | 0WMswq6eEtCN3GPO1mvHXGhcqd6R2nOZkxfVE0sOoRI56iz/GXqpOoVuF4Foj7BT 12 | XpAe8GUBdxe5mu28ogCvNdoyDyUxd+HgrV+7+WQYGcA9T88KC8WM4vdpVcuzQRrf 13 | 9NFvlZbGf1ZpWlCRzsg6kWU3TS3TGaz2ImZUVsLULDms6lDojaEezcHG9QARAQAB 14 | tDtFT1MgT1NUcmVlIFNpZ25pbmcgS2V5IDEgKEVPU0sxKSA8bWFpbnRhaW5lcnNA 15 | ZW5kbGVzc20uY29tPokCVQQTAQIAPwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgEC 16 | F4AWIQRjw/zTxI+/EWovSsyeCNjaugL8RgUCXRzVQwUJHHUdGwAKCRCeCNjaugL8 17 | RjC4EACQX8b2nq5b28Vu1SNFizdRARh6+Zn3FzPVaU9UEAHbs+vjCCbuKVs5y0ZG 18 | jt549niTXIXVVuBMw3NndTB2vzXm+rRLX2CnL/0WDYKcbx3qah5+W7nDLShS016d 19 | CBTuw/6CNzg5+yVZbWU7Fi+5Ebfvsk0UQHhXCxk3sukgFmSWIp7tm4/vekER1hfu 20 | L7IVXprzIl4t0Z/ixx1pMu8+6Gvbucq8jUTtE49t0tF3pQZuwECPOJgTA9HFEERc 21 | kyyNqC8bsQa1pnTZMpgo7onL4kzS0ZTOQcO0rE6jaPJu9ZB8iNlVqPTDJ8N8miO/ 22 | 6l11rMK0lxHnF6vjvcttWE+Em5ls+B1J1RmJKk66DukJn/ZE8gKBRyj9ZmW4sxat 23 | OAtwPsg3ml0xjwqL4BLj03KKnaz/o/THWzlQyKM1WUAJ3fV+28tZNvYfXEkWgIFb 24 | /EZGGh6dhE+GWkwpDbm90UZd4GUvEIOaSg16w39Yj2kGLPUWfZtEW8vQTfVLP1dI 25 | GXS/hTUfo1oi1WpjENEJFONntJUMTxpNitjkq75kdt7ihHMD4kC0cLdiTHNnWCVl 26 | fNFMAPcTQaEJkSP6KEAYFSX1bqVZGPplt+5wE4G/oV4h4QjylkzJovEhbC+6dEFR 27 | yY82fonAdBFWQWkDETqPpZEX/46BwUBUdB4p3YjgmqKoZrIhHg== 28 | =Sic7 29 | -----END PGP PUBLIC KEY BLOCK----- 30 | -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0001.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0002.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0003.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0004.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0005.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0006.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0007.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0008.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0009.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0010.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0011.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0012.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0013.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0014.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0015.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0016.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0017.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0018.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0019.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0020.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0021.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0022.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0023.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0024.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0025.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0026.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0027.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0028.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0029.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0030.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0031.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0032.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0033.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0034.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0035.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/animation-0036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/animation-0036.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/background-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/background-tile.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/box.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/bullet.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/entry.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/lock.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/spinner.plymouth: -------------------------------------------------------------------------------- 1 | [Plymouth Theme] 2 | Name=Spinner 3 | Description=A theme designed by jimmac that features a simple spinner. 4 | ModuleName=two-step 5 | 6 | [two-step] 7 | ImageDir=/usr/share/plymouth/themes/spinner 8 | HorizontalAlignment=.5 9 | VerticalAlignment=.75 10 | Transition=none 11 | TransitionDuration=0.0 12 | BackgroundStartColor=0x12282E 13 | BackgroundEndColor=0x12282E 14 | WatermarkHorizontalAlignment=.5 15 | WatermarkVerticalAlignment=.4 16 | -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0001.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0002.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0003.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0004.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0005.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0006.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0007.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0008.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0009.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0010.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0011.png -------------------------------------------------------------------------------- /data/plymouth/themes/spinner/throbber-0012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/plymouth/themes/spinner/throbber-0012.png -------------------------------------------------------------------------------- /data/rpi-common/LICENCE.broadcom: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Broadcom Corporation. 2 | Copyright (c) 2015, Raspberry Pi (Trading) Ltd 3 | All rights reserved. 4 | 5 | Redistribution. Redistribution and use in binary form, without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * This software may only be used for the purposes of developing for, 10 | running or using a Raspberry Pi device, or authorised derivative 11 | device manufactured via the element14 Raspberry Pi Customization Service 12 | * Redistributions must reproduce the above copyright notice and the 13 | following disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | * Neither the name of Broadcom Corporation nor the names of its suppliers 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 20 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 21 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 22 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 26 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 28 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 29 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 30 | DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- /data/rpi-common/bootcode.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/bootcode.bin -------------------------------------------------------------------------------- /data/rpi-common/config.txt: -------------------------------------------------------------------------------- 1 | enable_uart=1 2 | -------------------------------------------------------------------------------- /data/rpi-common/fixup.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup.dat -------------------------------------------------------------------------------- /data/rpi-common/fixup4.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup4.dat -------------------------------------------------------------------------------- /data/rpi-common/fixup4cd.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup4cd.dat -------------------------------------------------------------------------------- /data/rpi-common/fixup4db.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup4db.dat -------------------------------------------------------------------------------- /data/rpi-common/fixup4x.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup4x.dat -------------------------------------------------------------------------------- /data/rpi-common/fixup_cd.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup_cd.dat -------------------------------------------------------------------------------- /data/rpi-common/fixup_db.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/fixup_db.dat -------------------------------------------------------------------------------- /data/rpi-common/start.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start.elf -------------------------------------------------------------------------------- /data/rpi-common/start4.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start4.elf -------------------------------------------------------------------------------- /data/rpi-common/start4cd.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start4cd.elf -------------------------------------------------------------------------------- /data/rpi-common/start4db.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start4db.elf -------------------------------------------------------------------------------- /data/rpi-common/start4x.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start4x.elf -------------------------------------------------------------------------------- /data/rpi-common/start_cd.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start_cd.elf -------------------------------------------------------------------------------- /data/rpi-common/start_db.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi-common/start_db.elf -------------------------------------------------------------------------------- /data/rpi4/bcm2711-rpi-4-b.dtb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi4/bcm2711-rpi-4-b.dtb -------------------------------------------------------------------------------- /data/rpi4/bcm2711-rpi-400.dtb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/rpi4/bcm2711-rpi-400.dtb -------------------------------------------------------------------------------- /data/s905x-bootloader/acs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s905x-bootloader/acs.bin -------------------------------------------------------------------------------- /data/s905x-bootloader/bl2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s905x-bootloader/bl2.bin -------------------------------------------------------------------------------- /data/s905x-bootloader/bl21.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s905x-bootloader/bl21.bin -------------------------------------------------------------------------------- /data/s905x-bootloader/bl30.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s905x-bootloader/bl30.bin -------------------------------------------------------------------------------- /data/s905x-bootloader/bl301.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s905x-bootloader/bl301.bin -------------------------------------------------------------------------------- /data/s905x-bootloader/bl31.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s905x-bootloader/bl31.img -------------------------------------------------------------------------------- /data/s912-bootloader/acs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s912-bootloader/acs.bin -------------------------------------------------------------------------------- /data/s912-bootloader/bl2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s912-bootloader/bl2.bin -------------------------------------------------------------------------------- /data/s912-bootloader/bl21.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s912-bootloader/bl21.bin -------------------------------------------------------------------------------- /data/s912-bootloader/bl30.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s912-bootloader/bl30.bin -------------------------------------------------------------------------------- /data/s912-bootloader/bl301.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s912-bootloader/bl301.bin -------------------------------------------------------------------------------- /data/s912-bootloader/bl31.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/data/s912-bootloader/bl31.img -------------------------------------------------------------------------------- /eos-image-builder: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- mode: Python; coding: utf-8 -*- 3 | 4 | # Endless image build runner 5 | # 6 | # Copyright (C) 2014-2015 Endless Mobile, Inc. 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with this program; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | from argparse import ArgumentParser 23 | import errno 24 | import fcntl 25 | import os 26 | import subprocess 27 | import sys 28 | import time 29 | 30 | MYDIR = os.path.dirname(os.path.realpath(__file__)) 31 | RUN_BUILD = os.path.join(MYDIR, 'run-build') 32 | sys.path.insert(1, os.path.join(MYDIR, 'lib')) 33 | import eib # noqa: E402 34 | 35 | LOCKTIMEOUT = 60 36 | 37 | 38 | def set_close_on_exec(fd): 39 | flags = fcntl.fcntl(fd, fcntl.F_GETFD) 40 | flags |= fcntl.FD_CLOEXEC 41 | fcntl.fcntl(fd, fcntl.F_SETFD, flags) 42 | 43 | 44 | def lock_builder(lockf, timeout): 45 | wait = timeout 46 | while True: 47 | try: 48 | # Grab the lock exclusively non-blocking 49 | fcntl.flock(lockf.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) 50 | break 51 | except IOError as err: 52 | if err.errno in [errno.EACCES, errno.EAGAIN]: 53 | if wait == timeout: 54 | lockpid = lockf.read().strip() 55 | print(lockf.name, 'currently locked by pid', lockpid, 56 | file=sys.stderr) 57 | wait -= 1 58 | if wait <= 0: 59 | raise Exception('Could not lock file %s' % lockf.name) 60 | time.sleep(1) 61 | else: 62 | # Some other issue 63 | raise 64 | 65 | # Write this pid into the lock file 66 | lockf.truncate() 67 | lockf.write('%d\n' % os.getpid()) 68 | lockf.flush() 69 | 70 | 71 | # This option list must stay in sync with run-build. It's repeated here 72 | # so that the branch to checkout can be reliably determined. 73 | aparser = ArgumentParser(description='Build and publish images for Endless') 74 | eib.add_cli_options(aparser) 75 | args = aparser.parse_args() 76 | 77 | # Shortcut to show the configuration without taking the lock. 78 | if args.show_config or args.show_apps: 79 | exit(subprocess.call([RUN_BUILD] + sys.argv[1:])) 80 | 81 | # Open the lock file a+ so it can be RW without truncating, but seek to 82 | # the beginning to either read or write the whole file. 83 | os.makedirs(os.path.dirname(eib.LOCKFILE), exist_ok=True) 84 | with open(eib.LOCKFILE, 'a+') as lf: 85 | lf.seek(0) 86 | lock_builder(lf, args.lock_timeout) 87 | 88 | # Set the close-on-exec bit so the lock file isn't inherited by 89 | # child processes. This isn't actually required since subprocess 90 | # will close all fds except stdin/out/err by default, but let's be 91 | # safe. 92 | set_close_on_exec(lf.fileno()) 93 | 94 | # Run the real builder from the checkout 95 | subprocess.check_call([RUN_BUILD] + sys.argv[1:]) 96 | -------------------------------------------------------------------------------- /helpers/assemble-manifest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Assemble all the json fragments in the manifest directory and write 4 | # out a merged json file. 5 | 6 | import json 7 | import os 8 | import sys 9 | 10 | 11 | def dict_merge(target, source): 12 | """Deep merge of dictionaries 13 | 14 | See http://blog.impressiver.com/post/31434674390/deep-merge-multiple-python-dicts 15 | 16 | >>> dict_merge({}, {}) == {} 17 | True 18 | >>> dict_merge({'a': {'c': 1}}, 19 | ... {'a': {'d': 2}}, 20 | ... ) == {'a': {'c': 1, 'd': 2}} 21 | True 22 | >>> dict_merge({'a': {'b': 1}}, 23 | ... {'a': {'b': 2}}) 24 | {'a': {'b': 2}} 25 | >>> dict_merge({'a': 'not a dict'}, 26 | ... {'a': {'b': 2}}) 27 | {'a': {'b': 2}} 28 | >>> dict_merge({'a': {'b': 2}}, 29 | ... {'a': 'not a dict'}) 30 | Traceback (most recent call last): 31 | ... 32 | TypeError: ('a', 'is not a dict in source:', 'not a dict') 33 | """ 34 | for key, value in source.items(): 35 | if key in target and isinstance(target[key], dict): 36 | if not isinstance(value, dict): 37 | raise TypeError(key, 'is not a dict in source:', value) 38 | dict_merge(target[key], value) 39 | else: 40 | target[key] = value 41 | 42 | return target 43 | 44 | 45 | manifest_dir = os.environ['EIB_MANIFESTDIR'] 46 | merged_data = {} 47 | for root, dirs, files in os.walk(manifest_dir): 48 | # Sort in case deterministic json fragment order is needed 49 | dirs.sort() 50 | for f in sorted(files): 51 | json_path = os.path.join(root, f) 52 | print('Merging', json_path, file=sys.stderr) 53 | with open(json_path) as json_file: 54 | json_data = json.load(json_file) 55 | 56 | dict_merge(merged_data, json_data) 57 | 58 | print(json.dumps(merged_data, sort_keys=True)) 59 | -------------------------------------------------------------------------------- /helpers/create-grub-images: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | . "${EIB_BASELIB}" 4 | 5 | cleanup() { 6 | if [ -n "${img_loop}" ]; then 7 | eib_partx_delete "${img_loop}" 8 | eib_delete_loop "${img_loop}" 9 | fi 10 | rm -f "${img}" 11 | rm -f "${deploy}"/usr/lib/grub/i386-pc/eltorito.img 12 | rm -f "${deploy}"/usr/lib/grub/i386-pc/core.img 13 | rm -f "${deploy}"/usr/lib/grub/i386-pc/eosldr.mbr 14 | } 15 | trap cleanup EXIT 16 | 17 | create_grub_images() { 18 | local deploy=$1 19 | local boot_zip_dir=$2 20 | 21 | img="${EIB_TMPDIR}/${EIB_OUTVERSION}-grub-images.img" 22 | rm -f "${img}" 23 | truncate -s 70M "${img}" 24 | 25 | ( 26 | echo -n "start=2048, " 27 | echo "size=62MiB, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B" 28 | echo "size=1MiB, type=21686148-6449-6E6F-744E-656564454649" 29 | ) | sfdisk --force --label gpt "${img}" 30 | 31 | img_loop=$(losetup -f --show "${img}") 32 | eib_partx_scan "${img_loop}" 33 | 34 | local bios_boot_loop=${img_loop}p2 35 | 36 | local EMBEDDED_MODULES=" 37 | biosdisk 38 | ext2 39 | exfat 40 | ntfs 41 | part_msdos 42 | part_gpt 43 | " 44 | 45 | local MEMDISK_MODULES=" 46 | normal 47 | search 48 | configfile 49 | regexp 50 | " 51 | 52 | local ISO_EMBEDDED_MODULES=" 53 | ${EMBEDDED_MODULES} 54 | iso9660 55 | " 56 | # Capture BIOS-boot GRUB boot.img and core.img for live+installer USBs. 57 | # 58 | # grub-install invokes grub-mkimage, which writes a core.img file to 59 | # /boot/grub/i386-pc/; it then invokes grub-bios-setup to write this 60 | # file to the BIOS boot partition, and write an appropriate MBR (boot.img, 61 | # as shipped with GRUB, patched with the offset to the BIOS boot 62 | # partition). 63 | # 64 | # When dealing with standalone images instead of grub-mkimage, 65 | # grub-mkstandalone is used. 66 | # 67 | # You might hope that we could just save core.img somewher, and later 68 | # dd it verbatim onto the BIOS boot partition, but it transpires that 69 | # grub-bios-setup does more than this: it patches the partition offset 70 | # here too, and (if there's extra space, which there is here) adds some 71 | # redundancy to the image. 72 | # 73 | # We could disable the latter with --no-rs-codes, but we'd still have to 74 | # add the offsets. So we actually install this flavour of GRUB, 75 | # capturing the boot sector and BIOS boot partition. 76 | # 77 | # Also create BIOS-boot GRUB core.img for images on NTFS. Windows only supports 78 | # BIOS-booting from MBR partition tables, so the pristine boot.img and 79 | # core.img (which both have a 1-sector offset embedded in them) are fine. 80 | chroot "${deploy}" /usr/bin/grub-mkstandalone \ 81 | --directory=/usr/lib/grub/i386-pc \ 82 | --format=i386-pc \ 83 | --themes= \ 84 | --fonts= \ 85 | --modules="${EMBEDDED_MODULES}" \ 86 | --install-modules="${MEMDISK_MODULES}" \ 87 | --output=/usr/lib/grub/i386-pc/core.img \ 88 | /boot/grub/grub.cfg='/usr/lib/grub/conf/grub_embedded_image.cfg' 89 | chroot "${deploy}" /usr/sbin/grub-bios-setup \ 90 | --directory=/usr/lib/grub/i386-pc \ 91 | ${img_loop} 92 | 93 | mkdir -p "${boot_zip_dir}/live" 94 | dd if="${img_loop}" of="${boot_zip_dir}/live/boot.img" bs=446 count=1 95 | dd if="${bios_boot_loop}" of="${boot_zip_dir}/live/core.img" bs=512 count=2048 96 | 97 | mkdir -p "${boot_zip_dir}/ntfs" 98 | cp -a "${deploy}/usr/lib/grub/i386-pc/boot.img" "${boot_zip_dir}/ntfs" 99 | cp -a "${deploy}/usr/lib/grub/i386-pc/core.img" "${boot_zip_dir}/ntfs" 100 | 101 | # Save boot_hybrid.img MBR code for the hybrid ISO and the El-Torito boot image 102 | chroot "${deploy}" /usr/bin/grub-mkstandalone \ 103 | --directory=/usr/lib/grub/i386-pc \ 104 | --format=i386-pc-eltorito \ 105 | --themes= \ 106 | --fonts= \ 107 | --modules="${ISO_EMBEDDED_MODULES}" \ 108 | --install-modules="${MEMDISK_MODULES}" \ 109 | --output=/usr/lib/grub/i386-pc/eltorito.img \ 110 | /boot/grub/grub.cfg='/usr/lib/grub/conf/grub_embedded_image.cfg' 111 | 112 | mkdir -p "${boot_zip_dir}/iso" 113 | cp -a "${deploy}/usr/lib/grub/i386-pc/boot_hybrid.img" "${boot_zip_dir}/iso" 114 | cp -a "${deploy}/usr/lib/grub/i386-pc/eltorito.img" "${boot_zip_dir}/iso" 115 | 116 | # Generate an eosldr.mbr file (up to 8k) to be chainloaded by the 117 | # Windows bootloader. 118 | # This binary has just enough NTFS knowledge to find the 2nd stage 119 | # eosldr and chainload it. eosldr is our core.img + a special header 120 | # shipped by lnxboot.img. 121 | chroot "${deploy}" /usr/bin/grub-ntldr-img \ 122 | --grub2 \ 123 | --boot-file=eosldr \ 124 | --output /usr/lib/grub/i386-pc/eosldr.mbr 125 | 126 | mkdir -p "${boot_zip_dir}/eosldr" 127 | cp -a "${deploy}/usr/lib/grub/i386-pc/eosldr.mbr" "${boot_zip_dir}/eosldr" 128 | cat "${deploy}/usr/lib/grub/i386-pc/lnxboot.img" "${deploy}/usr/lib/grub/i386-pc/core.img" \ 129 | > "${boot_zip_dir}/eosldr/eosldr" 130 | } 131 | 132 | # Run main function for this image. 133 | create_grub_images "$@" 134 | -------------------------------------------------------------------------------- /helpers/create-iso: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | . "${EIB_BASELIB}" 4 | 5 | # Write a bootable ISO image containing the image file, Windows tool and other 6 | # files needed to install Endless OS in every supported configuration. 7 | create_iso() { 8 | local version=${1:?No OS version for $FUNCNAME} 9 | shift 10 | local iso=${1:?No target filename $FUNCNAME} 11 | shift 12 | local img=${1:?No image supplied to $FUNCNAME} 13 | shift 14 | local boot_zip=${1:?No boot.zip supplied to $FUNCNAME} 15 | shift 16 | local img_csum=${1:?No image checksum supplied to $FUNCNAME} 17 | shift 18 | local boot_zip_csum=${1:?No boot.zip checksum supplied to $FUNCNAME} 19 | shift 20 | 21 | local ISO_TMPDIR="$EIB_TMPDIR/iso" 22 | local DIR_EFI="$ISO_TMPDIR/efi" 23 | local DIR_IMAGES="$ISO_TMPDIR/images" 24 | local DIR_ESP="$ISO_TMPDIR/esp" 25 | local DIR_IMAGES_ENDLESS="${DIR_IMAGES}/endless" 26 | mkdir -p "$DIR_EFI" "$DIR_ESP" "$DIR_IMAGES_ENDLESS" 27 | 28 | ln "$boot_zip" "${DIR_IMAGES_ENDLESS}/$(basename "$boot_zip")" 29 | ln "$img_csum" "${DIR_IMAGES_ENDLESS}/$(basename "$img_csum")" 30 | ln "$boot_zip_csum" "${DIR_IMAGES_ENDLESS}/$(basename "$boot_zip_csum")" 31 | 32 | # Link img.asc, boot.zip.asc into ISO tree if they exist. During manual 33 | # testing, the GPG key may not be available in which case no signatures will 34 | # be generated -- allow that here. 35 | for i in "$@"; do 36 | if [ -e "$i" ]; then 37 | ln "$i" "${DIR_IMAGES_ENDLESS}/$(basename "$i")" 38 | fi 39 | done 40 | 41 | echo -n "${EIB_OUTVERSION}.img" > "${DIR_IMAGES_ENDLESS}/live" 42 | 43 | # Trim duplication of product name from personality name (eg fnde_aluno) 44 | personality=${EIB_PERSONALITY#${EIB_PRODUCT}_} 45 | 46 | # Set label for ISO - including the personality name "bare" is not ideal 47 | # but generally speaking we are limited to 32 characters, and the image 48 | # builder is unaware of localised personality names 49 | label="${EIB_IMAGE_PRODUCT_NAME} ${version} ${personality}" 50 | 51 | # TODO: Perhaps we should create a package in OBS which just contains 52 | # endless-installer.exe? 53 | local ei_url="https://images-dl.endlessm.com/endless-installer/endless-installer.exe" 54 | local ei_exe="${DIR_IMAGES}/endless-installer.exe" 55 | wget -O "${ei_exe}" "${ei_url}" 56 | 57 | # This is for Windows' benefit, so it should be in utf-16 with CRLF line endings 58 | sed 's/$/\r/' < "${DIR_IMAGES}/autorun.inf" 59 | [AutoRun] 60 | label=${label} 61 | icon=endless-installer.exe 62 | open=endless-installer.exe 63 | 64 | [Content] 65 | MusicFiles=false 66 | PictureFiles=false 67 | VideoFiles=false 68 | AUTORUN_INF 69 | 70 | # Publish size of image inside the ISO (& squashfs). This is the interesting 71 | # number that can't be easily determined from the size of the ISO itself. 72 | # We will delete $img after creating the squashfs to reduce 73 | # peak disk usage, so we need to determine its size beforehand. 74 | local img_size=$(stat -c "%s" "${img}") 75 | echo "${img_size}" > "${iso}.size" 76 | 77 | local squashfs_comp=gzip 78 | if [ "${EIB_IMAGE_COMPRESSION}" == "xz" ]; then 79 | squashfs_comp=xz 80 | fi 81 | 82 | # Create squashfs containing just $img, under the name endless.img 83 | ln "$img" "${DIR_IMAGES_ENDLESS}/endless.img" 84 | local squash="${DIR_IMAGES_ENDLESS}/endless.squash" 85 | mksquashfs "${DIR_IMAGES_ENDLESS}/endless.img" "${squash}" \ 86 | -comp $squashfs_comp \ 87 | -b 131072 88 | rm -f "${DIR_IMAGES_ENDLESS}/endless.img" 89 | 90 | # At this point, the raw image is no longer needed for the ISO 91 | # creation, but we need to keep it for now in case other assets need 92 | # to be created from it. 93 | 94 | local squash_asc="${DIR_IMAGES_ENDLESS}/${EIB_OUTVERSION}.squash.asc" 95 | local squash_csum="${DIR_IMAGES_ENDLESS}/${EIB_OUTVERSION}.squash.sha256" 96 | sign_file "$squash" "$squash_asc" & 97 | checksum_file "$squash" "$squash_csum" & 98 | wait 99 | 100 | # Construct ESP 101 | unzip -q -d "${DIR_EFI}" "${boot_zip}" "EFI/*" 102 | local DIR_EFI_SIZE=$(du -s "${DIR_EFI}" | cut -f1) 103 | local ESP_SIZE=$(( (DIR_EFI_SIZE + 1024) / 1024 * 1024 )) 104 | 105 | # Create ESP and copy the EFI content 106 | truncate -s ${ESP_SIZE}K "${DIR_ESP}/efi.img" 107 | mkdosfs "${DIR_ESP}/efi.img" 108 | mcopy -s -i "${DIR_ESP}/efi.img" "${DIR_EFI}/EFI" '::/' 109 | 110 | # Unpack generic and ISO-specific GRUB files 111 | unzip -q -d "${DIR_IMAGES_ENDLESS}" "${boot_zip}" "grub/*" 112 | unzip -q -d "${DIR_IMAGES_ENDLESS}/grub/i386-pc/" "${boot_zip}" "iso/*" 113 | 114 | local VOLID=$(echo -n "$label" | tr -cs 'A-Za-z0-9_' '-') 115 | 116 | # Generate the ISO image. 117 | # Parameters found using https://dev.lovelyhq.com/libburnia/libisoburn/raw/master/frontend/grub-mkrescue-sed.sh 118 | # 119 | # export MKRESCUE_SED_MODE=mbr_only 120 | # export MKRESCUE_SED_PROTECTIVE=no 121 | # export MKRESCUE_SED_DEBUG=yes 122 | # 123 | # grub-mkrescue -o output.iso minimal_directory --xorriso=grub-mkrescue-sed.sh 124 | 125 | xorriso -as mkisofs \ 126 | -o "${iso}" \ 127 | -r -graft-points -no-pad \ 128 | --sort-weight 0 / \ 129 | --sort-weight 1 /endless \ 130 | -b endless/grub/i386-pc/iso/eltorito.img \ 131 | -no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info \ 132 | --grub2-mbr "${DIR_IMAGES_ENDLESS}/grub/i386-pc/iso/boot_hybrid.img" \ 133 | -eltorito-alt-boot \ 134 | -e --interval:appended_partition_2:all:: \ 135 | -no-emul-boot \ 136 | -append_partition 2 0xef "${DIR_ESP}/efi.img" \ 137 | -iso-level 3 \ 138 | -joliet -joliet-long \ 139 | -volid "${VOLID::32}" \ 140 | -publisher 'Endless OS Foundation LLC' \ 141 | "${DIR_IMAGES}" 142 | 143 | # Change partition type from 0x83 to 0x00 144 | printf "\x00" | dd of="${iso}" bs=1 count=1 seek=450 conv=notrunc 145 | 146 | # Clean up! 147 | rm -fr "${ISO_TMPDIR}" 148 | 149 | if [ "${EIB_ISO_MAX_SIZE}" ]; then 150 | local iso_size=$(stat --printf='%s' "${iso}") 151 | if [ "${iso_size}" -gt "${EIB_ISO_MAX_SIZE}" ]; then 152 | local excess=$(( "${iso_size}" - "${EIB_ISO_MAX_SIZE}" )) 153 | echo "ERROR: ${iso} is ${iso_size} bytes," \ 154 | "${excess} bytes above limit of ${EIB_ISO_MAX_SIZE} bytes" >&2 155 | return 1 156 | fi 157 | fi 158 | 159 | # Sign and checksum ISO 160 | sign_file "${iso}" & 161 | checksum_file "${iso}" & 162 | wait 163 | 164 | # Generate ISO manifest information 165 | local iso_name=$(basename "${iso}") 166 | local iso_compressed_size=$(stat -c "%s" "${iso}") 167 | local iso_signature=$(basename "${iso}.asc") 168 | local iso_checksum=$(basename "${iso}.sha256") 169 | 170 | cat > "${EIB_MANIFESTDIR}"/iso.json < "${EIB_MANIFESTDIR}"/ovf.json < "${EIB_MANIFESTDIR}"/kalite.json </etc/systemd/system-preset/40-eos-firewall-localonly.preset < "${OSTREE_DEPLOYMENT}"/etc/hostname 7 | -------------------------------------------------------------------------------- /hooks/image/50-initramfs-append: -------------------------------------------------------------------------------- 1 | # create /boot/initramfs-append if a custom plymouth theme is needed 2 | # 3 | # this will be added to the kernel command line as a 2nd initramfs 4 | # at boot time anything contained here will be unpacked in the initramfs 5 | # in addition to the default contents, so we can overwrite the plymouth 6 | # settings file and add our own theme 7 | 8 | # skip script if no watermark is required 9 | if [ -z "${EIB_IMAGE_INITRAMFS_PLYMOUTH_WATERMARK}" ]; then 10 | exit 0 11 | fi 12 | 13 | tmpdir=$(mktemp -d -p "$EIB_TMPDIR") 14 | plymouthdir="usr/share/plymouth" 15 | plymouthcustomizationdir="eos-customization/plymouth" 16 | 17 | pushd ${tmpdir} 18 | 19 | mkdir -p "${plymouthcustomizationdir}/themes" 20 | 21 | cp "${OSTREE_DEPLOYMENT}/${plymouthdir}/plymouthd.defaults" "${plymouthcustomizationdir}" 22 | sed -i "s/^Theme=.*/Theme=spinner/" "${plymouthcustomizationdir}/plymouthd.defaults" 23 | 24 | cp -r "${EIB_DATADIR}/plymouth/themes/spinner" "${plymouthcustomizationdir}/themes" 25 | cp "${EIB_IMAGE_INITRAMFS_PLYMOUTH_WATERMARK}" "${plymouthcustomizationdir}/themes/spinner/watermark.png" 26 | 27 | find . | cpio -o -H newc | gzip -9 -n >"${EIB_OSTREE_CHECKOUT}/boot/initramfs-append" 28 | 29 | popd 30 | 31 | rm -rf "${tmpdir}" 32 | 33 | -------------------------------------------------------------------------------- /hooks/image/50-kalite: -------------------------------------------------------------------------------- 1 | # Put KA Lite's content in the right place and write default config 2 | 3 | kalitehome=${OSTREE_VAR}/lib/kalite 4 | preloaded=${kalitehome}/PRELOADED 5 | 6 | # Skip if the kalite helper is not installed 7 | if ! test -f ${OSTREE_DEPLOYMENT}/lib/systemd/system/eos-kalite-system-helper.socket; then 8 | exit 0 9 | fi 10 | 11 | rm -rf "${kalitehome}"/* 12 | 13 | # Skip check if no sample media needed 14 | if [ "${EIB_IMAGE_KALITE_CONTENT}" != true ]; then 15 | exit 0 16 | fi 17 | 18 | # We need to convert from a locale (e.g. pt_BR.utf8) to 19 | # a language code matching our s3 bucket (e.g. pt). 20 | lang=`echo ${EIB_IMAGE_LANGUAGE} | sed -r 's/_.*//' ` 21 | echo "LANGUAGE: ${lang}" 22 | 23 | # Copy the videos to PRELOADED 24 | mkdir -p "${preloaded}"/content 25 | pushd "${preloaded}" 26 | tar Jxvf "${EIB_CONTENTDIR}"/kalite/"${lang}"/first-run-data.xz 27 | cp -r "${EIB_CONTENTDIR}"/kalite/"${lang}"/* content/ 28 | popd 29 | 30 | # set content permissions 31 | chown -R $(ostree_uid kalite):$(ostree_gid kalite) "${preloaded}" 32 | find "${preloaded}" -type f -exec chmod 664 '{}' ';' 33 | chmod 775 ${preloaded} 34 | -------------------------------------------------------------------------------- /hooks/image/50-language: -------------------------------------------------------------------------------- 1 | # Set LANG in /etc/locale.conf for the default language. 2 | if [ -n "${EIB_IMAGE_LANGUAGE}" ]; then 3 | echo "LANG=${EIB_IMAGE_LANGUAGE}" > ${OSTREE_DEPLOYMENT}/etc/locale.conf 4 | fi 5 | 6 | # Dump it into the manifest too 7 | cat > "${EIB_MANIFESTDIR}"/image-language.json < "${EIB_MANIFESTDIR}"/flatpak-locales.json < "${conf_file}" 6 | sed -i '/^server_url *=/d' "${conf_file}" 7 | echo "server_url=${EIB_IMAGE_METRICS_SERVER_URL}" >> "${conf_file}" 8 | fi 9 | 10 | if [ -n "${EIB_IMAGE_ACTIVATION_SERVER_URL}" ]; then 11 | conf_file="${OSTREE_DEPLOYMENT}"/etc/eos-phone-home.conf 12 | [ -f "${conf_file}" ] || echo "[global]" > "${conf_file}" 13 | sed -i '/^host *=/d' "${conf_file}" 14 | echo "host=${EIB_IMAGE_ACTIVATION_SERVER_URL}" >> "${conf_file}" 15 | fi 16 | -------------------------------------------------------------------------------- /hooks/image/50-reclaim-swap-stamp: -------------------------------------------------------------------------------- 1 | # Create the /var/eos-swap-reclaimed file so that eos-reclaim-swap.service does 2 | # not run on first boot. Swap partitions are not created on new images so there 3 | # is no storage space to be reclaimed. 4 | touch -r ${OSTREE_DEPLOYMENT}/usr ${OSTREE_VAR}/eos-swap-reclaimed 5 | -------------------------------------------------------------------------------- /hooks/image/50-safe-defaults.chroot: -------------------------------------------------------------------------------- 1 | mkdir -p /etc/systemd/system-preset 2 | cat >/etc/systemd/system-preset/40-eos-safe-defaults.preset < ${OSTREE_DEPLOYMENT}/etc/timezone 8 | fi 9 | -------------------------------------------------------------------------------- /hooks/image/50-update-done-stamp: -------------------------------------------------------------------------------- 1 | # Create the /etc/.updated and /var/.updated files so that the systemd 2 | # ConditionNeedsUpdate services don't run on first boot. 3 | 4 | touch -r ${OSTREE_DEPLOYMENT}/usr ${OSTREE_DEPLOYMENT}/etc/.updated 5 | touch -r ${OSTREE_DEPLOYMENT}/usr ${OSTREE_VAR}/.updated 6 | -------------------------------------------------------------------------------- /hooks/image/50-update-server.chroot: -------------------------------------------------------------------------------- 1 | # makes the system into a local network server for ostree updates 2 | 3 | mkdir -p /usr/local/share/eos-updater 4 | cat >/usr/local/share/eos-updater/eos-update-server.conf </etc/systemd/system-preset/40-eos-update-server.preset </usr/local/share/eos-updater/eos-updater.conf </usr/local/share/eos-updater/eos-autoupdater.conf </usr/local/share/eos-updater/eos-updater.conf </usr/local/share/eos-updater/eos-autoupdater.conf < ${TARGET_PATH}/00-keyboard.conf 10 | Section "InputClass" 11 | Identifier "system-keyboard" 12 | MatchIsKeyboard "on" 13 | Option "XkbLayout" "${EIB_IMAGE_XKB_LAYOUT}" 14 | EOF 15 | 16 | if [ -n "${EIB_IMAGE_XKB_VARIANT}" ]; then 17 | cat <<- EOF >> ${TARGET_PATH}/00-keyboard.conf 18 | Option "XkbVariant" "${EIB_IMAGE_XKB_VARIANT}" 19 | EOF 20 | fi 21 | 22 | cat <<- EOF >> ${TARGET_PATH}/00-keyboard.conf 23 | EndSection 24 | EOF 25 | 26 | # Write to /etc/default/keyboard too, Debian patches 27 | # localed to read and write from this location 28 | 29 | cat <<- EOF >> ${DEBIAN_DEFAULTS_PATH}/keyboard 30 | XKBLAYOUT=${EIB_IMAGE_XKB_LAYOUT} 31 | EOF 32 | 33 | if [ -n "${EIB_IMAGE_XKB_VARIANT}" ]; then 34 | cat <<- EOF >> ${DEBIAN_DEFAULTS_PATH}/keyboard 35 | XKBVARIANT=${EIB_IMAGE_XKB_VARIANT} 36 | EOF 37 | fi 38 | fi 39 | -------------------------------------------------------------------------------- /hooks/image/53-ek-content-preload: -------------------------------------------------------------------------------- 1 | # Populate the Endless Key home directory 2 | 3 | if [ -z "${EIB_ENDLESSKEY_COLLECTIONS}" ]; then 4 | exit 0 5 | fi 6 | 7 | if [[ ! "${EIB_FLATPAK_REMOTE_FLATHUB_APPS}" =~ .*"org.endlessos.Key".* ]]; then 8 | exit 0 9 | fi 10 | 11 | selected_collection_files=() 12 | 13 | channels_file="${EIB_TMPDIR}"/ek-channels 14 | rm -f "${channels_file}" 15 | touch "${channels_file}" 16 | all_collection_files="${OSTREE_VAR}"/lib/flatpak/app/org.endlessos.Key/current/active/files/share/endless-key/collections/*.json 17 | for collection_file in ${all_collection_files}; do 18 | # Check if the file basename stripped off of -0001.json is part of the list 19 | # of collections to be installed. 20 | bn=$(basename "${collection_file%-????.json}") 21 | if [[ " ${EIB_ENDLESSKEY_COLLECTIONS} " =~ [[:space:]]${bn}[[:space:]] ]] ; then 22 | selected_collection_files+=("${collection_file}") 23 | jq -r '.channels[].id' "${collection_file}" >> "${channels_file}" 24 | fi 25 | done 26 | 27 | all_channels=$(sort -u "${channels_file}") 28 | if [ -z "${all_channels}" ]; then 29 | echo "No Kolibri channels to preload" 30 | exit 0 31 | fi 32 | 33 | # Seed the needed channels on the content server. 34 | "${EIB_HELPERSDIR}"/seed-kolibri-channels ${all_channels} 35 | 36 | venv_dir="${EIB_TMPDIR}/kolibri-content-venv" 37 | python3 -m venv ${venv_dir} 38 | source ${venv_dir}/bin/activate 39 | pip install "${EIB_ENDLESSKEY_KOLIBRI_PKGSPEC}" 40 | pip install kolibri-app-desktop-xdg-plugin==${EIB_ENDLESSKEY_KOLIBRI_APP_DESKTOP_XDG_PLUGIN_VERSION} 41 | 42 | # Setup the homedir before setting any environment variables so they 43 | # don't persist into the options file. 44 | export KOLIBRI_HOME="${OSTREE_VAR}"/lib/endless-key/data 45 | mkdir -p "${KOLIBRI_HOME}" 46 | kolibri plugin enable kolibri_app_desktop_xdg_plugin 47 | kolibri configure setup 48 | 49 | # Use a separate content URL if configured. 50 | if [ -n "${EIB_KOLIBRI_CENTRAL_CONTENT_BASE_URL}" ]; then 51 | KOLIBRI_CENTRAL_CONTENT_BASE_URL="${EIB_KOLIBRI_CENTRAL_CONTENT_BASE_URL}" 52 | export KOLIBRI_CENTRAL_CONTENT_BASE_URL 53 | fi 54 | 55 | # kolibri-app-desktop-xdg-plugin uses FLATPAK_ID to determine how to name the 56 | # launchers it creates. This is set by Flatpak when running the app, but we are 57 | # not running kolibri from within the Endless Key Flatpak here. 58 | export FLATPAK_ID=org.endlessos.Key 59 | 60 | # Import all channel metadata and thumbnails for all channels 61 | for channel in $all_channels; do 62 | kolibri manage --skip-update importchannel network "${channel}" 63 | EIB_RETRY_ATTEMPTS=2 EIB_RETRY_INTERVAL=30 eib_retry \ 64 | kolibri manage --skip-update \ 65 | importcontent --include-unrenderable-content --fail-on-error \ 66 | --node_ids="" --all-thumbnails \ 67 | network "${channel}" 68 | done 69 | 70 | if [ "${EIB_ENDLESSKEY_INCLUDE_FULL_CHANNELS}" == true ]; then 71 | for channel in $all_channels; do 72 | EIB_RETRY_ATTEMPTS=2 EIB_RETRY_INTERVAL=30 eib_retry \ 73 | kolibri manage --skip-update \ 74 | importcontent --include-unrenderable-content --fail-on-error \ 75 | network "${channel}" 76 | done 77 | else 78 | for collection_file in "${selected_collection_files[@]}"; do 79 | collection_channels=$(jq -r '.channels[].id' "${collection_file}") 80 | for channel in $collection_channels; do 81 | EIB_RETRY_ATTEMPTS=2 EIB_RETRY_INTERVAL=30 eib_retry \ 82 | kolibri manage --skip-update \ 83 | importcontent --include-unrenderable-content --fail-on-error \ 84 | --manifest="${collection_file}" network "${channel}" 85 | done 86 | done 87 | fi 88 | 89 | # Empty the user database, and ensure that each instance of this image has a 90 | # unique Facility ID. 91 | # 92 | (echo yes; echo yes) | kolibri manage --skip-update deprovision 93 | 94 | # Hack the .desktop files to work around them being generated differently when 95 | # the xdg plugin is running in the Flatpak, which it is not here. 96 | for desktop_file in "${OSTREE_VAR}"/lib/endless-key/data/content/xdg/share/applications/*.desktop; do 97 | sed -i -e 's/x-kolibri-dispatch:/x-endless-key-dispatch:/g' "${desktop_file}" 98 | desktop-file-edit \ 99 | --set-key=TryExec \ 100 | --set-value=/var/lib/flatpak/app/org.endlessos.Key/current/active/files/bin/kolibri-gnome \ 101 | "${desktop_file}" 102 | done 103 | 104 | # Chown all the files to the kolibri user. This also happens at runtime 105 | # via the endless-key.conf tmpfiles.d configuration. 106 | kolibri_uid=$(ostree_uid kolibri) 107 | kolibri_gid=$(ostree_gid kolibri) 108 | chown -R "${kolibri_uid}:${kolibri_gid}" "${OSTREE_VAR}"/lib/endless-key 109 | -------------------------------------------------------------------------------- /hooks/image/54-ek-launchers: -------------------------------------------------------------------------------- 1 | # Expose Endless Key launchers in the app grid (once a specific content pack has been downloaded) 2 | 3 | mkdir -p "${OSTREE_DEPLOYMENT}/etc/systemd/user-environment-generators" 4 | cat <<- 'EOF' > "${OSTREE_DEPLOYMENT}/etc/systemd/user-environment-generators/62-endless-key.sh" 5 | #!/bin/bash 6 | 7 | XDG_DATA_DIRS="/var/lib/endless-key/data/content/xdg/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" 8 | echo "XDG_DATA_DIRS=$XDG_DATA_DIRS" 9 | EOF 10 | chmod 755 "${OSTREE_DEPLOYMENT}/etc/systemd/user-environment-generators/62-endless-key.sh" 11 | -------------------------------------------------------------------------------- /hooks/image/60-chromium-policies: -------------------------------------------------------------------------------- 1 | # Add custom policies to the chrome/chromium filetree 2 | 3 | [ "${EIB_IMAGE_CHROMIUM_POLICIES}" = true ] || exit 0 4 | 5 | CHROME_DIR="${OSTREE_DEPLOYMENT}"/etc/opt/chrome/policies 6 | CHROMIUM_DIR="${OSTREE_DEPLOYMENT}"/etc/chromium-browser/policies 7 | 8 | if [ -n "${EIB_IMAGE_CHROMIUM_POLICIES_MANAGED}" ]; then 9 | mkdir -p "${CHROME_DIR}"/managed 10 | mkdir -p "${CHROMIUM_DIR}"/managed 11 | cp "${EIB_IMAGE_CHROMIUM_POLICIES_MANAGED}" "${CHROME_DIR}"/managed 12 | cp "${EIB_IMAGE_CHROMIUM_POLICIES_MANAGED}" "${CHROMIUM_DIR}"/managed 13 | fi 14 | 15 | if [ -n "${EIB_IMAGE_CHROMIUM_POLICIES_RECOMMENDED}" ]; then 16 | mkdir -p "${CHROME_DIR}"/recommended 17 | mkdir -p "${CHROMIUM_DIR}"/recommended 18 | cp "${EIB_IMAGE_CHROMIUM_POLICIES_RECOMMENDED}" "${CHROME_DIR}"/recommended 19 | cp "${EIB_IMAGE_CHROMIUM_POLICIES_RECOMMENDED}" "${CHROMIUM_DIR}"/recommended 20 | fi 21 | -------------------------------------------------------------------------------- /hooks/image/60-dconf-prepare: -------------------------------------------------------------------------------- 1 | # Copy dconf overrides to the correct location in the image 2 | 3 | TGT=${OSTREE_DEPLOYMENT}/tmp/dconf-overrides 4 | mkdir -p ${TGT} 5 | 6 | LOCKS_DIR=${TGT}/locks 7 | mkdir -p ${LOCKS_DIR} 8 | 9 | for file in ${EIB_IMAGE_SETTINGS}; do 10 | cp ${file} ${TGT} 11 | done 12 | 13 | for key in ${EIB_IMAGE_SETTINGS_LOCKS}; do 14 | cp ${key} ${LOCKS_DIR} 15 | done 16 | -------------------------------------------------------------------------------- /hooks/image/60-flatpak-autoinstall-counters.chroot: -------------------------------------------------------------------------------- 1 | # Run the flatpak auto-installer that usually runs on system upgrades in 2 | # "stamp" mode. This will inspect 3 | # /usr/share/eos-application-tools/flatpak-autoinstall.d/* and determine 4 | # the most recent stamp revision to apply in 5 | # /var/lib/eos-application-tools/flatpak-autoinstall.d/*, but won't actually 6 | # install any packages. The second invocation with --mode check will ensure that 7 | # the expected flatpaks have either been installed or removed, so that the 8 | # autoinstall list doesn't get out of sync with the flatpaks that have 9 | # actually been installed on the system. 10 | 11 | [ "${EIB_FLATPAK_ENABLE}" = true ] || exit 0 12 | /usr/libexec/eos-updater-flatpak-installer --mode stamp 13 | /usr/libexec/eos-updater-flatpak-installer --mode check 14 | 15 | -------------------------------------------------------------------------------- /hooks/image/60-kolibri-content: -------------------------------------------------------------------------------- 1 | # Populate the Kolibri home directory 2 | 3 | if [ -z "${EIB_KOLIBRI_INSTALL_CHANNELS}" ]; then 4 | exit 0 5 | fi 6 | 7 | import_kolibri_channel() 8 | { 9 | local channel_id=$1 10 | local channel_include_node_ids_var="EIB_KOLIBRI_${channel_id^^}_INCLUDE_NODE_IDS" 11 | local channel_exclude_node_ids_var="EIB_KOLIBRI_${channel_id^^}_EXCLUDE_NODE_IDS" 12 | local importcontent_opts=( 13 | # By default, importcontent skips content nodes that it doesn't 14 | # think are renderable. Since we don't know what renderers will be 15 | # available at runtime, request everything. 16 | --include-unrenderable-content 17 | 18 | # Normally importcontent ignores download errors. Make it fail so we 19 | # can be sure we've fully provisioned channels. 20 | --fail-on-error 21 | ) 22 | local importcontent_network_opts=( 23 | # The default timeout is 60 seconds, but downloading can be slow 24 | # when objects aren't in our content CDN yet. 25 | --timeout 300 26 | ) 27 | 28 | if [ -n "${!channel_include_node_ids_var}" ]; then 29 | importcontent_include_nodes=$(echo "${!channel_include_node_ids_var}" | xargs | tr -s ' ' ',') 30 | importcontent_opts+=(--node_ids="${importcontent_include_nodes}") 31 | fi 32 | 33 | if [ -n "${!channel_exclude_node_ids_var}" ]; then 34 | importcontent_exclude_nodes=$(echo "${!channel_exclude_node_ids_var}" | xargs | tr -s ' ' ',') 35 | importcontent_opts+=(--exclude_node_ids="${importcontent_exclude_nodes}") 36 | fi 37 | 38 | kolibri manage --skip-update importchannel network "${channel_id}" 39 | EIB_RETRY_ATTEMPTS=2 EIB_RETRY_INTERVAL=30 eib_retry \ 40 | kolibri manage --skip-update importcontent "${importcontent_opts[@]}" \ 41 | network "${importcontent_network_opts[@]}" "${channel_id}" 42 | } 43 | 44 | # Seed the needed channels on the content server. 45 | "${EIB_HELPERSDIR}"/seed-kolibri-channels ${EIB_KOLIBRI_INSTALL_CHANNELS} 46 | 47 | venv_dir="${EIB_TMPDIR}/kolibri-content-venv" 48 | python3 -m venv ${venv_dir} 49 | source ${venv_dir}/bin/activate 50 | 51 | pip install kolibri==${EIB_KOLIBRI_APP_VERSION} 52 | pip install kolibri-app-desktop-xdg-plugin==${EIB_KOLIBRI_APP_DESKTOP_XDG_PLUGIN_VERSION} 53 | pip install kolibri-desktop-auth-plugin==${EIB_KOLIBRI_DESKTOP_AUTH_PLUGIN_VERSION} 54 | 55 | export KOLIBRI_HOME="${OSTREE_VAR}"/lib/kolibri/data 56 | rm -rf "${KOLIBRI_HOME}" 57 | mkdir -p "${KOLIBRI_HOME}" 58 | 59 | kolibri plugin enable kolibri.plugins.app 60 | kolibri plugin enable kolibri_app_desktop_xdg_plugin 61 | kolibri plugin enable kolibri_desktop_auth_plugin 62 | 63 | # Setup the homedir before setting any environment variables so they 64 | # don't persist into the options file. 65 | kolibri configure setup 66 | 67 | # Use a separate content URL if configured. 68 | if [ -n "${EIB_KOLIBRI_CENTRAL_CONTENT_BASE_URL}" ]; then 69 | KOLIBRI_CENTRAL_CONTENT_BASE_URL="${EIB_KOLIBRI_CENTRAL_CONTENT_BASE_URL}" 70 | export KOLIBRI_CENTRAL_CONTENT_BASE_URL 71 | fi 72 | 73 | # Do not create symlinks for static files inside the image builder. 74 | export KOLIBRI_STATIC_USE_SYMLINKS=0 75 | 76 | for channel_id in ${EIB_KOLIBRI_INSTALL_CHANNELS}; do 77 | import_kolibri_channel "${channel_id}" 78 | done 79 | 80 | # Sort channels in the same order as in EIB_KOLIBRI_INSTALL_CHANNELS 81 | position=1 82 | for channel_id in ${EIB_KOLIBRI_INSTALL_CHANNELS}; do 83 | kolibri manage --skip-update setchannelposition ${channel_id} ${position} || true 84 | let position=position+1 85 | done 86 | 87 | # Empty the user database, and ensure that each instance of this image has a 88 | # unique Facility ID. 89 | # 90 | (echo yes; echo yes) | kolibri manage --skip-update deprovision 91 | -------------------------------------------------------------------------------- /hooks/image/61-dconf-compile.chroot: -------------------------------------------------------------------------------- 1 | # Compiles the dconf overrides database and puts it in the location expected 2 | # by our dconf default user profile 3 | 4 | OVERRIDES_DIR=/tmp/dconf-overrides 5 | TARGET_DIR=/var/lib/eos-image-defaults 6 | TARGET_DB=${TARGET_DIR}/settings 7 | 8 | mkdir -p ${TARGET_DIR} 9 | dconf compile ${TARGET_DB} ${OVERRIDES_DIR} 10 | -------------------------------------------------------------------------------- /hooks/image/62-kolibri-automatic-provision: -------------------------------------------------------------------------------- 1 | # Configure Kolibri automatic provisioning if enabled 2 | 3 | if [ "${EIB_KOLIBRI_AUTOMATIC_PROVISION}" != "true" ]; then 4 | exit 0 5 | fi 6 | 7 | mkdir -p "${OSTREE_VAR}"/lib/kolibri/data 8 | 9 | cat < "${OSTREE_VAR}"/lib/kolibri/data/automatic_provision.json 10 | { 11 | "facility_name": "${EIB_KOLIBRI_AUTOMATIC_PROVISION_FACILITY_NAME}", 12 | "superuser": { 13 | "username": "${EIB_KOLIBRI_AUTOMATIC_PROVISION_SUPERUSER_NAME}", 14 | "password": "${EIB_KOLIBRI_AUTOMATIC_PROVISION_SUPERUSER_PASSWORD}" 15 | }, 16 | "preset": "${EIB_KOLIBRI_AUTOMATIC_PROVISION_PRESET}", 17 | "facility_settings": { 18 | "learner_can_edit_username": ${EIB_KOLIBRI_AUTOMATIC_PROVISION_LEARNER_CAN_EDIT_USERNAME}, 19 | "learner_can_edit_name": ${EIB_KOLIBRI_AUTOMATIC_PROVISION_LEARNER_CAN_EDIT_NAME}, 20 | "learner_can_edit_password": ${EIB_KOLIBRI_AUTOMATIC_PROVISION_LEARNER_CAN_EDIT_PASSWORD}, 21 | "learner_can_sign_up": ${EIB_KOLIBRI_AUTOMATIC_PROVISION_LEARNER_CAN_SIGN_UP} 22 | }, 23 | "device_settings": { 24 | "landing_page": "${EIB_KOLIBRI_AUTOMATIC_PROVISION_LANDING_PAGE}", 25 | "allow_guest_access": ${EIB_KOLIBRI_AUTOMATIC_PROVISION_ALLOW_GUEST_ACCESS}, 26 | "allow_other_browsers_to_connect": 0 27 | } 28 | } 29 | EOF 30 | -------------------------------------------------------------------------------- /hooks/image/62-kolibri-options: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Create a default options.ini for Kolibri 4 | 5 | import configparser 6 | import functools 7 | import os 8 | from pathlib import Path 9 | 10 | OSTREE_VAR = Path(os.environ.get("OSTREE_VAR")) 11 | KOLIBRI_HOME = Path(OSTREE_VAR, "lib/kolibri/data") 12 | OPTIONS_FILE_PATH = Path(KOLIBRI_HOME, "options.ini") 13 | 14 | EIB_KOLIBRI_REGULAR_USERS_CAN_MANAGE_CONTENT = os.environ.get( 15 | "EIB_KOLIBRI_REGULAR_USERS_CAN_MANAGE_CONTENT" 16 | ) 17 | 18 | config = configparser.ConfigParser() 19 | 20 | # By default, ConfigParser converts option names to a lowercase "canonical" 21 | # form. Instead, we want it to leave them alone. 22 | config.optionxform = lambda name: name 23 | 24 | if EIB_KOLIBRI_REGULAR_USERS_CAN_MANAGE_CONTENT == "true": 25 | try: 26 | config.add_section("DesktopAuth") 27 | except configparser.DuplicateSectionError: 28 | pass 29 | config.set("DesktopAuth", "REGULAR_USERS_CAN_MANAGE_CONTENT", "True") 30 | 31 | config_count = functools.reduce( 32 | lambda total, section: total + len(section), config.values(), 0 33 | ) 34 | 35 | if config_count > 0: 36 | with open(OPTIONS_FILE_PATH, "w") as options_file: 37 | config.write(options_file) 38 | -------------------------------------------------------------------------------- /hooks/image/63-icon-grid: -------------------------------------------------------------------------------- 1 | # Copy icon-grid overrides to the correct location in the image 2 | 3 | TGT=${OSTREE_VAR}/lib/eos-image-defaults/icon-grid 4 | mkdir -p ${TGT} 5 | 6 | for file in ${EIB_IMAGE_ICON_GRID}; do 7 | if json-glib-validate ${file}; then 8 | cp ${file} ${TGT} 9 | else 10 | echo "Invalid JSON file: ${file}" 11 | exit 1 12 | fi 13 | done 14 | -------------------------------------------------------------------------------- /hooks/image/63-kolibri-chown.chroot: -------------------------------------------------------------------------------- 1 | # Chown all Kolibri data files to the kolibri user. This also happens at 2 | # runtime via the eos-kolibri.conf tmpfiles.d configuration. 3 | if [ -d /var/lib/kolibri ]; then 4 | chown -R kolibri:kolibri /var/lib/kolibri 5 | fi 6 | -------------------------------------------------------------------------------- /hooks/image/70-flatpak-appstream-catalog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Construct AppStream XML for preinstalled apps 4 | 5 | import logging 6 | import os 7 | import subprocess 8 | 9 | from xml.etree import ElementTree 10 | 11 | import gi 12 | 13 | gi.require_version("Flatpak", "1.0") 14 | from gi.repository import GLib, Gio, Flatpak # noqa: E402 15 | 16 | logging.basicConfig( 17 | level=logging.DEBUG, 18 | format="+ %(asctime)s %(levelname)s %(name)s: %(message)s", 19 | datefmt="%H:%M:%S", 20 | ) 21 | logger = logging.getLogger(os.path.basename(__file__)) 22 | 23 | APPSTREAM_VERSION = "0.8" 24 | 25 | 26 | def main(): 27 | GLib.set_prgname(os.path.basename(__file__)) 28 | 29 | image_version = os.environ["EIB_OUTVERSION"] 30 | appstream_filename = image_version + ".appstream.xml" 31 | appstream_path = os.path.join(os.environ["EIB_OUTDIR"], appstream_filename) 32 | 33 | catalog = ElementTree.Element( 34 | "components", version=APPSTREAM_VERSION, origin=image_version 35 | ) 36 | 37 | system_path = os.path.join(os.environ["OSTREE_VAR"], "lib/flatpak") 38 | logger.info("Opening Flatpak installation in %s", system_path) 39 | system_file = Gio.File.new_for_path(system_path) 40 | system = Flatpak.Installation.new_for_path(system_file, user=False) 41 | 42 | installed_refs_by_origin_arch = {} 43 | for ref in system.list_installed_refs(): 44 | key = (ref.get_origin(), ref.get_arch()) 45 | installed_refs_by_origin_arch.setdefault(key, set()).add(ref.format_ref()) 46 | 47 | for (origin, arch), installed_refs in installed_refs_by_origin_arch.items(): 48 | path = os.path.join( 49 | system_path, "appstream", origin, arch, "active", "appstream.xml" 50 | ) 51 | 52 | try: 53 | remote_catalog = ElementTree.parse(path).getroot() 54 | except ElementTree.ParseError as e: 55 | # TODO: Should this be fatal? 56 | logger.warning("Failed to parse %s: %s", path, e) 57 | continue 58 | 59 | metainfo_version = remote_catalog.attrib["version"] 60 | if metainfo_version != APPSTREAM_VERSION: 61 | logger.warning( 62 | "Remote %s %s has AppStream version %s, not %s", 63 | origin, 64 | arch, 65 | metainfo_version, 66 | APPSTREAM_VERSION, 67 | ) 68 | 69 | logger.info("Adding components from %s", path) 70 | for component in remote_catalog: 71 | bundle = component.find("bundle[@type='flatpak']") 72 | try: 73 | installed_refs.remove(bundle.text) 74 | except KeyError: 75 | pass # Not installed 76 | else: 77 | catalog.append(component) 78 | 79 | # Anything left in the set is unexpectedly not present in the AppStream for the 80 | # remote it came from. It is normal for runtimes to not have metainfo; for 81 | # example, .Locale extensions never do. 82 | for ref in installed_refs: 83 | if ref.startswith("app/"): 84 | # TODO: Should this be fatal? 85 | logger.warning("No component found for %s from %s", ref, origin) 86 | 87 | tree = ElementTree.ElementTree(catalog) 88 | tree.write(appstream_path, encoding="unicode", xml_declaration=True) 89 | 90 | logger.info("Compressing %s to %s.gz", appstream_path, appstream_path) 91 | subprocess.run(["pigz", "-9", "-f", appstream_path], check=True) 92 | 93 | 94 | if __name__ == "__main__": 95 | main() 96 | -------------------------------------------------------------------------------- /hooks/image/70-flatpak-manifest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Populate manifest information about flatpak runtimes and 4 | # apps. 5 | 6 | from datetime import datetime, timezone 7 | from gi import require_version 8 | import json 9 | import os 10 | import sys 11 | 12 | require_version('Flatpak', '1.0') 13 | require_version('OSTree', '1.0') 14 | from gi.repository import Flatpak, GLib, Gio, OSTree # noqa: E402 15 | 16 | 17 | def commit_date_string(repo, checksum): 18 | """Get a formatted timestamp of the commit at checksum""" 19 | _, commit, _ = repo.load_commit(checksum) 20 | if sys.byteorder != 'big': 21 | # The commit variant is stored big endian 22 | commit = commit.byteswap() 23 | timestamp = commit[5] 24 | time = datetime.fromtimestamp(timestamp, timezone.utc) 25 | return time.strftime('%Y-%m-%dT%H:%M:%S%z') 26 | 27 | 28 | def write_manifest(data): 29 | """Write out the json to a fragment""" 30 | manifestdir = os.environ['EIB_MANIFESTDIR'] 31 | manifest_path = os.path.join(manifestdir, 'flatpak.json') 32 | print('Writing flatpak manifest info to', manifest_path) 33 | with open(manifest_path, 'w') as manifest: 34 | json.dump(data, manifest) 35 | 36 | 37 | def get_data_for_ref( 38 | repo: OSTree.Repo, 39 | installed_ref: Flatpak.InstalledRef, 40 | ) -> dict: 41 | name = installed_ref.get_name() 42 | arch = installed_ref.get_arch() 43 | branch = installed_ref.get_branch() 44 | key = '{}/{}/{}'.format(name, arch, branch) 45 | remote = installed_ref.get_origin() 46 | commit = installed_ref.get_commit() 47 | size = installed_ref.get_installed_size() 48 | ref = installed_ref.format_ref() 49 | date = commit_date_string(repo, commit) 50 | version = installed_ref.get_appdata_version() 51 | 52 | data = { 53 | 'name': name, 54 | 'arch': arch, 55 | 'branch': branch, 56 | 'remote': remote, 57 | 'commit': commit, 58 | 'size': size, 59 | 'ref': ref, 60 | 'date': date, 61 | 'version': version, 62 | } 63 | 64 | # TODO: It would be nice to consider EIB_FLATPAK_LOCALES and extract the 65 | # name and summary in those locales too. Unfortunately while there is 66 | # internal API to do this easily, it is not exposed by libflatpak. 67 | appdata_name = installed_ref.get_appdata_name() 68 | appdata_summary = installed_ref.get_appdata_summary() 69 | if appdata_name or appdata_summary: 70 | data['appdata'] = {'C': {}} 71 | if appdata_name: 72 | data['appdata']['C']['name'] = appdata_name 73 | if appdata_summary: 74 | data['appdata']['C']['summary'] = appdata_summary 75 | 76 | return key, data 77 | 78 | 79 | def main(): 80 | # Build the json data structure 81 | data = { 82 | 'flatpak': { 83 | 'remotes': {}, 84 | 'runtimes': {}, 85 | 'apps': {}, 86 | } 87 | } 88 | 89 | if os.environ.get('EIB_FLATPAK_ENABLE', 'false') != 'true': 90 | # Write out an empty manifest so it always exists 91 | write_manifest(data) 92 | return 93 | 94 | # Open the flatpak installation in the OS /var/lib/flatpak. 95 | system_path = os.path.join(os.environ['OSTREE_VAR'], 'lib/flatpak') 96 | print('Opening flatpak installation in', system_path) 97 | system_file = Gio.File.new_for_path(system_path) 98 | system = Flatpak.Installation.new_for_path(system_file, user=False) 99 | 100 | repo_file = system_file.get_child('repo') 101 | print('Opening ostree repo in', repo_file.get_path()) 102 | repo = OSTree.Repo.new(repo_file) 103 | repo.open() 104 | 105 | remotes = system.list_remotes() 106 | for remote in remotes: 107 | name = remote.get_name() 108 | url = remote.get_url() 109 | collection_id = remote.get_collection_id() 110 | 111 | # Skip disabled remotes 112 | if remote.get_disabled(): 113 | continue 114 | 115 | # Skip local remotes (e.g., external apps) 116 | if url.startswith('file://'): 117 | continue 118 | 119 | data['flatpak']['remotes'][name] = { 120 | 'url': url, 121 | 'collection-id': collection_id, 122 | } 123 | 124 | runtimes = system.list_installed_refs_by_kind(Flatpak.RefKind.RUNTIME) 125 | for runtime in runtimes: 126 | key, runtime_data = get_data_for_ref(repo, runtime) 127 | data['flatpak']['runtimes'][key] = runtime_data 128 | 129 | apps = system.list_installed_refs_by_kind(Flatpak.RefKind.APP) 130 | for app in apps: 131 | key, app_data = get_data_for_ref(repo, app) 132 | 133 | metadata = GLib.KeyFile() 134 | metadata.load_from_bytes(app.load_metadata(), GLib.KeyFileFlags.NONE) 135 | try: 136 | runtime = metadata.get_string('Application', 'runtime') 137 | except GLib.GError: 138 | runtime = None 139 | app_data['runtime'] = runtime 140 | 141 | data['flatpak']['apps'][key] = app_data 142 | 143 | # Now write out the json to a fragment 144 | write_manifest(data) 145 | 146 | 147 | if __name__ == '__main__': 148 | main() 149 | -------------------------------------------------------------------------------- /hooks/image/70-ostree-manifest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Populate manifest information about ostree. 4 | 5 | from datetime import datetime, timezone 6 | from gi import require_version 7 | import json 8 | import os 9 | import sys 10 | 11 | require_version('OSTree', '1.0') 12 | from gi.repository import Gio, OSTree # noqa: E402 13 | 14 | 15 | def commit_date_string(repo, checksum): 16 | """Get a formatted timestamp of the commit at checksum""" 17 | _, commit, _ = repo.load_commit(checksum) 18 | if sys.byteorder != 'big': 19 | # The commit variant is stored big endian 20 | commit = commit.byteswap() 21 | timestamp = commit[5] 22 | time = datetime.fromtimestamp(timestamp, timezone.utc) 23 | return time.strftime('%Y-%m-%dT%H:%M:%S%z') 24 | 25 | 26 | def ostree_get_version(): 27 | release_path = os.path.join( 28 | os.environ['OSTREE_DEPLOYMENT'], 29 | 'etc/os-release' 30 | ) 31 | with open(release_path) as release_file: 32 | for release_item in release_file: 33 | release_item = release_item.strip() 34 | 35 | key, value = release_item.split('=', 1) 36 | if key == 'VERSION': 37 | if value.startswith(("'", '"')) and value[0] == value[-1]: 38 | value = value[1:-1] 39 | 40 | return value 41 | 42 | 43 | def get_remote_collection_id(repo, remote): 44 | """Get the collection ID for the OS remote""" 45 | _, collection_id = repo.get_remote_option(remote, 'collection-id') 46 | return collection_id 47 | 48 | 49 | repo_path = os.path.join(os.environ['EIB_OSTREE_CHECKOUT'], 50 | 'ostree/repo') 51 | repo_file = Gio.File.new_for_path(repo_path) 52 | print('Opening ostree repository in', repo_path) 53 | repo = OSTree.Repo.new(repo_file) 54 | repo.open() 55 | 56 | # Build the json data 57 | remote = os.environ['EIB_OSTREE_REMOTE'] 58 | ref = os.environ['EIB_OSTREE_REF_DEPLOY'] 59 | data = { 60 | 'ostree': { 61 | 'remote': remote, 62 | 'ref': ref, 63 | 'url': os.environ['EIB_OSTREE_DEPLOY_URL'], 64 | 'os': os.environ['EIB_OSTREE_OS'], 65 | 'version': ostree_get_version(), 66 | } 67 | } 68 | 69 | # Get the commit and date 70 | _, commit = repo.resolve_rev('%s:%s' % (remote, ref), allow_noent=False) 71 | date = commit_date_string(repo, commit) 72 | data['ostree']['commit'] = commit 73 | data['ostree']['date'] = date 74 | 75 | # Get the collection ID. Will be None/null if not set. 76 | collection_id = get_remote_collection_id(repo, remote) 77 | data['ostree']['collection-id'] = collection_id 78 | 79 | # Now write out the json to a fragment 80 | manifestdir = os.environ['EIB_MANIFESTDIR'] 81 | manifest_path = os.path.join(manifestdir, 'ostree.json') 82 | print('Writing ostree manifest info to', manifest_path) 83 | with open(manifest_path, 'w') as manifest: 84 | json.dump(data, manifest) 85 | -------------------------------------------------------------------------------- /hooks/image/70-packages-manifest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Populate manifest information about installed packages 4 | 5 | import apt_pkg 6 | import apt 7 | import json 8 | import os 9 | 10 | # Initialize apt 11 | apt_pkg.init() 12 | 13 | # Use the dpkg status file from the ostree deployment 14 | dpkg_status = os.path.join(os.environ['OSTREE_DEPLOYMENT'], 15 | 'usr/share/dpkg/database/status') 16 | apt_pkg.config.set('Dir::State::status', dpkg_status) 17 | 18 | # Open an apt Cache and output data for each installed package 19 | cache = apt.Cache() 20 | data = dict(packages={}) 21 | packages = data['packages'] 22 | for pkg in sorted(cache): 23 | if pkg.installed is None: 24 | continue 25 | 26 | # If this is a multi-arch package, use the fullname with the 27 | # architecture to match output from dpkg-query. 28 | multi_arch = pkg.installed.record.get('Multi-Arch', '') 29 | if multi_arch == 'same': 30 | name = pkg.fullname 31 | else: 32 | name = pkg.shortname 33 | packages[name] = { 34 | 'version': pkg.installed.version, 35 | 'size': pkg.installed.installed_size, 36 | } 37 | 38 | # Now write out the json to a fragment 39 | manifestdir = os.environ['EIB_MANIFESTDIR'] 40 | manifest_path = os.path.join(manifestdir, 'packages.json') 41 | print('Writing packages manifest info to', manifest_path) 42 | with open(manifest_path, 'w') as manifest: 43 | json.dump(data, manifest, sort_keys=True) 44 | -------------------------------------------------------------------------------- /hooks/image/80-ldconfig-aux-cache.chroot: -------------------------------------------------------------------------------- 1 | # Rebuild the linker cache so that the aux cache is shipped on new 2 | # images in /var/cache/ldconfig. This allows the subsequent ldconfig run 3 | # on upgrade to be fast since it can use the existing aux cache. 4 | 5 | ldconfig -X 6 | -------------------------------------------------------------------------------- /hooks/publish/40-build-log: -------------------------------------------------------------------------------- 1 | # Copy the temporary build log to the output directory 2 | cp "${EIB_TMPDIR}"/build.txt "$(eib_outfile build.txt)" 3 | -------------------------------------------------------------------------------- /hooks/publish/42-sha256sums: -------------------------------------------------------------------------------- 1 | # Create a SHA256SUMS{,.gpg} file consisting of all the individual 2 | # checksum files. This is useful for verifying downloads when using 3 | # machinectl. Some of the checksums are created for files that are 4 | # embedded in other images, so make sure the original file exists. 5 | pushd "${EIB_OUTDIR}" 6 | for checksum in *.sha256; do 7 | read -r _ asset < "${checksum}" 8 | if [ -f "${asset}" ]; then 9 | cat "${checksum}" 10 | fi 11 | done > SHA256SUMS 12 | popd 13 | sign_file "${EIB_OUTDIR}/SHA256SUMS" "${EIB_OUTDIR}/SHA256SUMS.gpg" 14 | -------------------------------------------------------------------------------- /hooks/publish/45-publish-s3: -------------------------------------------------------------------------------- 1 | # Publish full image directory to S3. This needs to happen before 2 | # rsyncing to the server so that it doesn't begin to sync that build to 3 | # S3 concurrently. 4 | 5 | if [ -z "${EIB_PUBLISH_S3_BUCKET}" ]; then 6 | echo "S3 bucket not set; skipping publishing" 7 | exit 0 8 | fi 9 | 10 | src="${EIB_OUTDIR}" 11 | dest="s3://${EIB_PUBLISH_S3_BUCKET}/${EIB_PUBLISH_PATH}" 12 | region="${EIB_PUBLISH_S3_REGION}" 13 | 14 | # Delete the .inprogress file in the S3 bucket to indicate that this 15 | # build has finished publishing files. 16 | end_publishing() { 17 | aws --region="${region}" s3 rm "${dest}"/.inprogress 18 | } 19 | trap end_publishing EXIT 20 | 21 | # Delete the in progess image publishing directory on failure. 22 | fail_publishing() { 23 | aws --region="${region}" s3 rm --recursive "${dest}" 24 | } 25 | trap fail_publishing ERR 26 | 27 | # Create an empty .inprogress file and copy it to the S3 bucket to 28 | # indicate that this build has started publishing files. 29 | : >"${EIB_TMPDIR}"/.inprogress 30 | aws --region="${region}" s3 cp "${EIB_TMPDIR}"/.inprogress \ 31 | "${dest}"/.inprogress 32 | 33 | # Sync the output directory to the S3 bucket. It would be nice to use 34 | # the sync command, but we want to set Content-Disposition to attachment 35 | # for asc files (https://phabricator.endlessm.com/T20501) and the AWS 36 | # API does not allow updating metadata on an existing object after the 37 | # fact. Just loop ourselves. 38 | for f in "${src}"/*; do 39 | opts=() 40 | case "${f}" in 41 | *.asc) 42 | opts+=(--content-disposition=attachment) 43 | ;; 44 | 45 | *.xml.gz) 46 | # By default S3 will serve the file with Content-Type: application/xml 47 | opts+=(--content-type=application/gzip) 48 | ;; 49 | esac 50 | aws --region="${region}" s3 cp "${opts[@]}" "${f}" "${dest}/" 51 | done 52 | -------------------------------------------------------------------------------- /hooks/publish/50-publish: -------------------------------------------------------------------------------- 1 | # Publish full image directory 2 | 3 | if [ -z "${EIB_IMAGE_UPLOAD_API_HOST}" ]; then 4 | echo "Upload host not set; skipping publishing" 5 | exit 0 6 | fi 7 | 8 | srcdir="${EIB_OUTDIR}" 9 | destdir="${EIB_IMAGE_DESTDIR}" 10 | 11 | # Delete the .inprogress file on the remote image server to indicate 12 | # that this build has finished publishing files. 13 | end_publishing() { 14 | ssh ${EIB_SSH_OPTIONS} "${EIB_IMAGE_USER}@${EIB_IMAGE_UPLOAD_API_HOST}" \ 15 | rm -f "${destdir}"/.inprogress 16 | } 17 | trap end_publishing EXIT 18 | 19 | # Delete the in progess image publishing directory on failure. 20 | fail_publishing() { 21 | ssh ${EIB_SSH_OPTIONS} "${EIB_IMAGE_USER}@${EIB_IMAGE_UPLOAD_API_HOST}" \ 22 | rm -rf "${destdir}" 23 | } 24 | trap fail_publishing ERR 25 | 26 | # Create a .inprogress file on the remote image server to indicate that 27 | # this build has started publishing files. 28 | ssh ${EIB_SSH_OPTIONS} "${EIB_IMAGE_USER}@${EIB_IMAGE_UPLOAD_API_HOST}" \ 29 | mkdir -p "${destdir}" 30 | ssh ${EIB_SSH_OPTIONS} "${EIB_IMAGE_USER}@${EIB_IMAGE_UPLOAD_API_HOST}" \ 31 | touch "${destdir}"/.inprogress 32 | 33 | # Use rsync with --delay-updates so the files remain hidden until 34 | # publishing completes. 35 | rsync -av --delay-updates -e "ssh ${EIB_SSH_OPTIONS}" \ 36 | "${srcdir}/" "${EIB_IMAGE_USER}@${EIB_IMAGE_UPLOAD_API_HOST}:${destdir}" 37 | -------------------------------------------------------------------------------- /lib/eib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | shopt -s nullglob 4 | shopt -s extglob 5 | 6 | # Make sure ERR traps follow through to shell functions 7 | set -E 8 | 9 | # Show current script and time when xtrace (set -x) enabled. Will look 10 | # like "+ 10:13:40 run-build: some command". 11 | export PS4='+ \t ${BASH_SOURCE[0]##*/}: ' 12 | 13 | # Runs a command chrooted with our helper script. 14 | chroot () { 15 | "${EIB_HELPERSDIR}"/eib-chroot "$@" 16 | } 17 | 18 | # Run hooks under customization/ 19 | run_hooks() { 20 | local hook interpreter 21 | local hooksdir="${EIB_HOOKSDIR:-${EIB_SRCDIR}/hooks}" 22 | local -a hooksdirs 23 | local group=$1 24 | local install_root=$2 25 | 26 | echo "Running $group hooks" 27 | export EIB_HOOK_GROUP=$group 28 | 29 | # Sort enabled hooks 30 | eval local hooks="\${EIB_${group^^}_HOOKS}" 31 | local files=$(echo "${hooks}" | tr ' ' '\n' | sort) 32 | 33 | # If a local settings directory is provided, look there first 34 | if [ -n "$EIB_LOCALDIR" ]; then 35 | hooksdirs+=("${EIB_LOCALDIR}/hooks") 36 | fi 37 | hooksdirs+=("${hooksdir}") 38 | 39 | for hook in ${files}; do 40 | local d 41 | local found 42 | local hookpath 43 | 44 | found=false 45 | for d in "${hooksdirs[@]}"; do 46 | hookpath="${d}/${group}/${hook}" 47 | if [ -f "${hookpath}" ]; then 48 | found=true 49 | break 50 | fi 51 | done 52 | 53 | if ! "${found}"; then 54 | echo "Missing hook ${hook} not found in ${hooksdirs[*]}" >&2 55 | return 1 56 | fi 57 | 58 | if [ "${hook: -7}" == ".chroot" ]; then 59 | if [ -z "$install_root" ]; then 60 | echo "Skipping hook, no chroot available: ${hook}" 61 | continue 62 | fi 63 | 64 | echo "Run hook in chroot: ${hook}" 65 | [ -x "${hookpath}" ] && interpreter= || interpreter="bash -ex" 66 | mkdir -p $install_root/tmp 67 | cp ${hookpath} $install_root/tmp/${hook} 68 | chroot $install_root $interpreter /tmp/${hook} 69 | rm -f $install_root/tmp/${hook} 70 | continue 71 | fi 72 | 73 | echo "Run hook: ${hook}" 74 | 75 | if [ -x "${hookpath}" ]; then 76 | ${hookpath} 77 | else 78 | ( 79 | . ${hookpath} 80 | ) 81 | fi 82 | done 83 | } 84 | 85 | # Generate full path to output file 86 | eib_outfile() { 87 | echo ${EIB_OUTDIR}/${EIB_OUTVERSION}.$1 88 | } 89 | 90 | # Encode the original image version as an xattr of the root directory of 91 | # each partition. 92 | # Usage: 93 | eib_write_version_xattr() { 94 | attr -s eos-image-version -V "${EIB_OUTVERSION}" "$1" 95 | } 96 | 97 | # Declare the EIB_MOUNTS array, but don't reinitialize it. 98 | declare -a EIB_MOUNTS 99 | 100 | # Mount a filesystem and track the target mount point. 101 | eib_mount() { 102 | local target 103 | 104 | if [ $# -lt 2 ]; then 105 | echo "At least 2 arguments needed to $FUNCNAME" >&2 106 | return 1 107 | fi 108 | 109 | # The target is the last argument 110 | eval target="\${$#}" 111 | 112 | mkdir -p "${target}" 113 | mount "$@" 114 | 115 | EIB_MOUNTS+=("${target}") 116 | } 117 | 118 | # Unmount a mount point and remove it from tracking. 119 | eib_umount() { 120 | local target=${1:?No mount target supplied to $FUNCNAME} 121 | local -i n 122 | local -a new_mounts=() 123 | local found=false 124 | 125 | for mntpnt in "${EIB_MOUNTS[@]}"; do 126 | if [ "${mntpnt}" = "${target}" ]; then 127 | umount "${target}" 128 | found=true 129 | else 130 | # Build a new array with the remaining mounts 131 | new_mounts+=("${mntpnt}") 132 | fi 133 | done 134 | 135 | if $found; then 136 | # Assign the array to the new filtered version 137 | EIB_MOUNTS=("${new_mounts[@]}") 138 | else 139 | echo "Mount point ${target} not tracked in: ${EIB_MOUNTS[@]}" >&2 140 | return 1 141 | fi 142 | } 143 | 144 | # Unmount all tracked mount points. 145 | eib_umount_all() { 146 | local -i n 147 | 148 | # Work from the end of the array to unmount submounts first 149 | for ((n = ${#EIB_MOUNTS[@]} - 1; n >= 0; n--)); do 150 | umount "${EIB_MOUNTS[n]}" 151 | done 152 | 153 | # Clear and re-declare the array 154 | unset EIB_MOUNTS 155 | declare -a EIB_MOUNTS 156 | } 157 | 158 | eib_fix_boot_checksum() { 159 | local disk=${1:?No disk supplied to ${FUNCNAME}} 160 | local deploy=${2:?No deployment supplied to ${FUNCNAME}} 161 | 162 | [ -x "${deploy}"/usr/sbin/amlogic-fix-spl-checksum ] || return 0 163 | "${deploy}"/usr/sbin/amlogic-fix-spl-checksum "${disk}" 164 | } 165 | 166 | # Work around transient failures. The EIB_RETRY_ATTEMPTS and 167 | # EIB_RETRY_INTERVAL environment variables can be used to change the 168 | # defaults of 10 attempts with 1 second sleeps between attempts. 169 | eib_retry() { 170 | local i=0 171 | local max_retries=${EIB_RETRY_ATTEMPTS:-10} 172 | local interval=${EIB_RETRY_INTERVAL:-1} 173 | 174 | if [ $# -eq 0 ]; then 175 | echo "error: No command supplied to ${FUNCNAME}" >&2 176 | return 1 177 | fi 178 | 179 | while ! "$@" && (( ++i < max_retries )) ; do 180 | echo "$@ failed; retrying..." >&2 181 | sleep "$interval" 182 | done 183 | 184 | if (( i >= max_retries )); then 185 | echo "$@ failed ${max_retries} times; giving up" >&2 186 | return 1 187 | fi 188 | } 189 | 190 | # Run udevadm settle to wait for device events to be processed. Tell 191 | # udevadm to ignore that we're in a chroot since we expect the udev 192 | # control socket to be bind mounted into it. 193 | eib_udevadm_settle() { 194 | # If settle can't connect to the /run/udev/control socket, it will 195 | # simply return without an error. Print an error in that case but 196 | # carry on since skipping settle may not be fatal. 197 | if [ ! -e /run/udev/control ]; then 198 | echo '/run/udev/control does not exist when calling "udevadm settle"' >&2 199 | return 0 200 | fi 201 | 202 | # If the host udev version doesn't match the one in the build root, it 203 | # may fail so retry. In particular, this works around a race when 204 | # the host udev is older than version 242 but the build version is 205 | # newer. In that case, the host udevd closes the connection to the 206 | # control socket immediately after receiving the ping command. 207 | # However, newer udev sends a subsequent end of messages command and 208 | # may receive an EPIPE if that hasn't completed before udevd closes 209 | # the connection. 210 | # 211 | # Ultimately any errors are ignored in the hope that any device events 212 | # have been processed anyways. This is no different than when udevadm 213 | # settle was a no-op in the build root. 214 | # 215 | # https://phabricator.endlessm.com/T30938 216 | SYSTEMD_IGNORE_CHROOT=1 eib_retry udevadm settle || return 0 217 | } 218 | 219 | # Helpers for partx and losetup to work around races with device 220 | # activity that cause the commands to fail with EBUSY. 221 | eib_partx_scan() { 222 | eib_udevadm_settle 223 | eib_retry partx -a -v "$1" 224 | } 225 | 226 | eib_partx_delete() { 227 | eib_udevadm_settle 228 | eib_retry partx -d -v "$1" 229 | } 230 | 231 | eib_delete_loop() { 232 | eib_udevadm_settle 233 | eib_retry losetup -d "$1" 234 | } 235 | 236 | recreate_dir() { 237 | rm -rf $1 238 | mkdir -p $1 239 | } 240 | 241 | # Removes modifier, codeset and replace separator, so 242 | # that a code like br_FR.iso885915@euro becomes br-FR 243 | locale_to_iso_639_1() { 244 | local no_modifier=$(echo "${1}" | cut -d '@' -f1) 245 | local no_codeset=$(echo "${no_modifier}" | cut -d '.' -f1) 246 | echo "${no_codeset}" | sed s/'_'/'-'/ 247 | } 248 | 249 | # Read ID of named user account from ostree deployment 250 | ostree_uid() { 251 | grep ^${1}: ${OSTREE_DEPLOYMENT}/lib/passwd | cut -d : -f 3 252 | } 253 | 254 | # Read ID of named group from ostree deployment 255 | ostree_gid() { 256 | grep ^${1}: ${OSTREE_DEPLOYMENT}/lib/group | cut -d : -f 3 257 | } 258 | 259 | # Created a detached signature with gpg. 260 | sign_file() { 261 | local file=${1:?No file supplied to ${FUNCNAME}} 262 | local outfile=${2:-${file}.asc} 263 | 264 | if [ -n "${EIB_IMAGE_SIGNING_KEYID}" ]; then 265 | gpg --homedir=${EIB_SYSCONFDIR}/gnupg \ 266 | --armour \ 267 | --sign-with ${EIB_IMAGE_SIGNING_KEYID} \ 268 | --detach-sign \ 269 | --output "${outfile}" \ 270 | "${file}" 271 | fi 272 | } 273 | 274 | # Create a detached checksum with sha256sum. By default the target in 275 | # the checksum file is the basename of the file being checksummed. 276 | checksum_file() { 277 | local file=${1:?No file supplied to ${FUNCNAME}} 278 | local outfile=${2:-${file}.sha256} 279 | local target=${3:-${file##*/}} 280 | local checksum 281 | 282 | checksum=$(sha256sum "${file}" | cut -d' ' -f1) 283 | echo "${checksum} ${target}" > "${outfile}" 284 | } 285 | 286 | # Emulate the old ostree write-refs builtin where a local ref is forced 287 | # to the commit of another ref. 288 | ostree_write_refs() { 289 | local repo=${1:?No ostree repo supplied to ${FUNCNAME}} 290 | local src=${2:?No ostree source ref supplied to ${FUNCNAME}} 291 | local dest=${3:?No ostree dest ref supplied to ${FUNCNAME}} 292 | local destdir=${dest%/*} 293 | 294 | # Create the needed directory for the dest ref. 295 | mkdir -p "${repo}/refs/heads/${destdir}" 296 | 297 | # Copy the source ref file to the dest ref. 298 | cp -f "${repo}/refs/heads/${src}" "${repo}/refs/heads/${dest}" 299 | } 300 | 301 | # Compress an image according to the configured compression type. 302 | eib_compress_image() { 303 | case "${EIB_IMAGE_COMPRESSION}" in 304 | xz) 305 | # Memory is limited to 1GB so that we don't ENOMEM on 32 bit 306 | # builds and so the number of threads is limited on hosts with 307 | # lots of CPUs. This should still allow 12 threads when enough 308 | # memory and CPUs are available. 309 | xz -vv -M1G -T0 -4 -c "${1}" > "${2}" 310 | ;; 311 | gz) 312 | pigz --no-name -c "${1}" > "${2}" 313 | ;; 314 | *) 315 | echo "Unrecognized image compression ${EIB_IMAGE_COMPRESSION}" >&2 316 | return 1 317 | ;; 318 | esac 319 | } 320 | 321 | true 322 | -------------------------------------------------------------------------------- /lib/eibkolibri.py: -------------------------------------------------------------------------------- 1 | # Endless image builder library - Kolibri utilities 2 | # 3 | # Copyright © 2023 Endless OS Foundation LLC 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License along 16 | # with this program; if not, write to the Free Software Foundation, Inc., 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | import eib 20 | import enum 21 | import logging 22 | from netrc import netrc 23 | import os 24 | import requests 25 | from time import sleep 26 | from urllib.parse import urljoin, urlparse 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | 31 | class RemoteKolibri: 32 | """Kolibri remote instance""" 33 | 34 | class Series(enum.Enum): 35 | """Supported Kolibri server series""" 36 | KOLIBRI_0_15 = enum.auto() 37 | KOLIBRI_0_16 = enum.auto() 38 | 39 | def __init__(self, base_url, username, password): 40 | self.base_url = base_url 41 | 42 | # Start a requests session with the credentials. 43 | self.session = requests.Session() 44 | self.session.auth = (username, password) 45 | self.session.headers.update({ 46 | 'Content-Type': 'application/json', 47 | }) 48 | 49 | self.series = self._get_server_series() 50 | 51 | def import_channel(self, channel_id): 52 | """Import channel and content on remote Kolibri server""" 53 | if self.series == self.Series.KOLIBRI_0_15: 54 | return self._import_channel_0_15(channel_id) 55 | elif self.series == self.Series.KOLIBRI_0_16: 56 | return self._import_channel_0_16(channel_id) 57 | 58 | raise AssertionError('Unsupported server series') 59 | 60 | def _import_channel_0_15(self, channel_id): 61 | """Import channel and content on remote Kolibri 0.15 server""" 62 | # Import channel metadata. 63 | url = urljoin( 64 | self.base_url, 65 | 'api/tasks/tasks/startremotechannelimport/', 66 | ) 67 | data = {'channel_id': channel_id} 68 | logger.info(f'Importing channel {channel_id} metadata') 69 | with self.session.post(url, json=data) as resp: 70 | try: 71 | resp.raise_for_status() 72 | except requests.exceptions.HTTPError: 73 | logger.error('Failed to import channel: %s', resp.json()) 74 | raise 75 | job = resp.json() 76 | self._wait_for_job(job['id']) 77 | 78 | # Import channel content. 79 | url = urljoin( 80 | self.base_url, 81 | 'api/tasks/tasks/startremotecontentimport/', 82 | ) 83 | data = { 84 | 'channel_id': channel_id, 85 | # Fetch all nodes so that the channel is fully mirrored. 86 | 'renderable_only': False, 87 | 'fail_on_error': True, 88 | } 89 | logger.info(f'Importing channel {channel_id} content') 90 | with self.session.post(url, json=data) as resp: 91 | try: 92 | resp.raise_for_status() 93 | except requests.exceptions.HTTPError: 94 | logger.error('Failed to import content: %s', resp.json()) 95 | raise 96 | job = resp.json() 97 | self._wait_for_job(job['id']) 98 | 99 | def _import_channel_0_16(self, channel_id, update=False): 100 | """Import channel and content on remote Kolibri 0.16 server""" 101 | url = urljoin(self.base_url, 'api/tasks/tasks/') 102 | data = { 103 | 'type': 'kolibri.core.content.tasks.remoteimport', 104 | 'channel_id': channel_id, 105 | 'channel_name': 'unknown', 106 | 'update': update, 107 | # Fetch all nodes so that the channel is fully mirrored. 108 | 'renderable_only': False, 109 | 'fail_on_error': True, 110 | } 111 | logger.info(f'Importing channel {channel_id}') 112 | with self.session.post(url, json=data) as resp: 113 | try: 114 | resp.raise_for_status() 115 | except requests.exceptions.HTTPError: 116 | logger.error('Failed to import channel: %s', resp.json()) 117 | raise 118 | job = resp.json() 119 | self._wait_for_job(job['id']) 120 | 121 | def update_channel(self, channel_id): 122 | """Update channel and content on remote Kolibri server""" 123 | if self.series == self.Series.KOLIBRI_0_15: 124 | return self._update_channel_0_15(channel_id) 125 | elif self.series == self.Series.KOLIBRI_0_16: 126 | return self._update_channel_0_16(channel_id) 127 | 128 | raise AssertionError('Unsupported server series') 129 | 130 | def _update_channel_0_15(self, channel_id): 131 | """Update channel and content on remote Kolibri 0.15 server""" 132 | # Generate channel diff stats. 133 | url = urljoin(self.base_url, 'api/tasks/tasks/channeldiffstats/') 134 | data = {'channel_id': channel_id, 'method': 'network'} 135 | logger.info(f'Generating channel {channel_id} diff') 136 | with self.session.post(url, json=data) as resp: 137 | try: 138 | resp.raise_for_status() 139 | except requests.exceptions.HTTPError: 140 | logger.error( 141 | 'Failed to generate channel diff: %s', 142 | resp.json(), 143 | ) 144 | raise 145 | job = resp.json() 146 | self._wait_for_job(job['id']) 147 | 148 | # Update channel metadata and content. 149 | url = urljoin(self.base_url, 'api/tasks/tasks/startchannelupdate/') 150 | data = { 151 | 'channel_id': channel_id, 152 | 'sourcetype': 'remote', 153 | # Fetch all nodes so that the channel is fully mirrored. 154 | 'renderable_only': False, 155 | 'fail_on_error': True, 156 | } 157 | logger.info(f'Updating channel {channel_id} content') 158 | with self.session.post(url, json=data) as resp: 159 | try: 160 | resp.raise_for_status() 161 | except requests.exceptions.HTTPError: 162 | logger.error('Failed to update channel: %s', resp.json()) 163 | raise 164 | job = resp.json() 165 | self._wait_for_job(job['id']) 166 | 167 | def _update_channel_0_16(self, channel_id): 168 | """Update channel and content on remote Kolibri 0.15 server""" 169 | # Generate channel diff stats. 170 | url = urljoin(self.base_url, 'api/tasks/tasks/') 171 | data = { 172 | 'type': 'kolibri.core.content.tasks.remotechanneldiffstats', 173 | 'channel_id': channel_id, 174 | 'channel_name': 'unknown', 175 | } 176 | logger.info(f'Generating channel {channel_id} diff') 177 | with self.session.post(url, json=data) as resp: 178 | try: 179 | resp.raise_for_status() 180 | except requests.exceptions.HTTPError: 181 | logger.error('Failed to generate channel diff: %s', resp.json()) 182 | raise 183 | job = resp.json() 184 | self._wait_for_job(job['id']) 185 | 186 | # Update channel metadata and content. 187 | self._import_channel_0_16(channel_id, update=True) 188 | 189 | def seed_channel(self, channel_id): 190 | """Import or update channel and content on remote Kolibri server 191 | 192 | If the channel exists, it will be updated since Kolibri won't 193 | import new content nodes otherwise. An import is always run to 194 | ensure any nodes missed because of a previous failure are 195 | imported. 196 | """ 197 | if self._channel_exists(channel_id): 198 | self.update_channel(channel_id) 199 | self.import_channel(channel_id) 200 | 201 | def _get_server_series(self): 202 | """Determine the server Kolibri series""" 203 | url = urljoin(self.base_url, 'api/public/info/') 204 | with self.session.get(url) as resp: 205 | resp.raise_for_status() 206 | info = resp.json() 207 | 208 | kolibri_version = info.get('kolibri_version', '') 209 | logger.debug(f'Server Kolibri version: "{kolibri_version}"') 210 | if kolibri_version.startswith('0.15.'): 211 | return self.Series.KOLIBRI_0_15 212 | elif kolibri_version.startswith('0.16.'): 213 | return self.Series.KOLIBRI_0_16 214 | 215 | raise Exception(f'Unsupported remote Kolibri version "{kolibri_version}"') 216 | 217 | def _get_job_status(self, job_id): 218 | """Get remote Kolibri job status""" 219 | url = urljoin(self.base_url, f'api/tasks/tasks/{job_id}/') 220 | with self.session.get(url) as resp: 221 | resp.raise_for_status() 222 | return resp.json() 223 | 224 | def _wait_for_job(self, job_id): 225 | """Wait for remote Kolibri job to complete""" 226 | logger.debug(f'Waiting for job {job_id} to complete') 227 | last_marker = None 228 | while True: 229 | data = self._get_job_status(job_id) 230 | 231 | # See the kolibri.core.tasks.job.State class for potential states 232 | # https://github.com/learningequality/kolibri/blob/develop/kolibri/core/tasks/job.py#L17 233 | status = data['status'] 234 | if status == 'FAILED': 235 | logger.error( 236 | f'Job {job_id} failed: ' 237 | f'{data["exception"]}\n{data["traceback"]}' 238 | ) 239 | raise Exception(f'Job {job_id} failed') 240 | elif status == 'CANCELED': 241 | raise Exception(f'Job {job_id} cancelled') 242 | elif status == 'COMPLETED': 243 | if last_marker is None or last_marker < 100: 244 | logger.info('Progress: 100%') 245 | break 246 | 247 | pct = int(data['percentage'] * 100) 248 | marker = pct - pct % 5 249 | if last_marker is None or marker > last_marker: 250 | logger.info(f'Progress: {pct}%') 251 | last_marker = marker 252 | 253 | # Wait a bit before checking the status again. 254 | sleep(0.5) 255 | 256 | def _channel_exists(self, channel_id): 257 | """Check if channel exists on remote Kolibri server""" 258 | url = urljoin(self.base_url, f'api/content/channel/{channel_id}/') 259 | logger.debug(f'Checking if channel {channel_id} exists') 260 | with self.session.get(url) as resp: 261 | try: 262 | resp.raise_for_status() 263 | except requests.exceptions.HTTPError: 264 | if resp.status_code == 404: 265 | return False 266 | logger.error( 267 | 'Failed to check channel existence: %s', 268 | resp.json(), 269 | ) 270 | raise 271 | else: 272 | return True 273 | 274 | 275 | def seed_remote_channels(channel_ids): 276 | """Import channels and content on remote Kolibri server 277 | 278 | Seeding is skipped if a custom content server is not used or there 279 | are no credentials for the server. Returns True when channels were 280 | seeded and False otherwise. 281 | """ 282 | config = eib.get_config() 283 | 284 | base_url = config.get('kolibri', 'central_content_base_url', fallback=None) 285 | if not base_url: 286 | logger.info('Not using custom Kolibri content server') 287 | return False 288 | 289 | netrc_path = os.path.join(eib.SYSCONFDIR, 'netrc') 290 | if not os.path.exists(netrc_path): 291 | logger.info(f'No credentials in {netrc_path}') 292 | return False 293 | 294 | netrc_creds = netrc(netrc_path) 295 | host = urlparse(base_url).netloc 296 | creds = netrc_creds.authenticators(host) 297 | if not creds: 298 | logger.info(f'No credentials for {host} in {netrc_path}') 299 | return False 300 | username, _, password = creds 301 | 302 | remote = RemoteKolibri(base_url, username, password) 303 | for channel in channel_ids: 304 | logger.info(f'Seeding channel {channel} on {host}') 305 | remote.seed_channel(channel) 306 | 307 | return True 308 | -------------------------------------------------------------------------------- /lib/eibostree.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Endless image builder library - OSTree utilities 4 | # 5 | # Copyright (C) 2018 Endless Mobile, Inc. 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | import eib 22 | import logging 23 | from gi import require_version 24 | require_version('OSTree', '1.0') 25 | from gi.repository import GLib, OSTree # noqa: E402 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | 30 | def fetch_remote_collection_id(repo, remote): 31 | """Fetch the remote's collection ID from its summary file 32 | 33 | Fetch the remote's summary and look for the 34 | ostree.summary.collection-id value in the summary metadata. Return 35 | None if no value was found. 36 | 37 | Args: 38 | repo: An open OSTree.Repo 39 | remote: The OSTree remote name 40 | 41 | Returns: 42 | The collection ID string or None if one isn't set 43 | """ 44 | logger.info('Fetching OSTree summary for remote %s', remote) 45 | _, summary_bytes, _ = eib.retry(repo.remote_fetch_summary, remote) 46 | summary_variant_type = GLib.VariantType.new( 47 | OSTree.SUMMARY_GVARIANT_STRING) 48 | summary = GLib.Variant.new_from_bytes(summary_variant_type, 49 | summary_bytes, False) 50 | 51 | # Look for collection ID key in the metadata. This is 52 | # OSTREE_SUMMARY_COLLECTION_ID in ostree, but that's not exported. 53 | summary_metadata = summary[1] 54 | collection_id = summary_metadata.get('ostree.summary.collection-id') 55 | if collection_id is None: 56 | logger.info('No collection ID in %s summary', remote) 57 | else: 58 | logger.info('Found collection ID "%s" in %s summary', 59 | collection_id, remote) 60 | return collection_id 61 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | # Run in verbose mode by default. Use -q to bump the verbosity down. 3 | addopts = -v 4 | 5 | # Enable debug logging by default to help track down issues. This is set 6 | # on the root logger, but it seems the only current log messages are 7 | # from our modules. The alternative is to use the caplog fixture on all 8 | # tests to just set the level on our loggers. 9 | log_level = DEBUG 10 | 11 | # Raise an error if tests marked xfail pass 12 | xfail_strict = True 13 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | # Python requirements for testing 2 | flake8 3 | pytest 4 | requests 5 | requests-mock 6 | -------------------------------------------------------------------------------- /stages/eib_error: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | . "${EIB_BASELIB}" 4 | 5 | run_hooks error 6 | -------------------------------------------------------------------------------- /stages/eib_manifest: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | . "${EIB_BASELIB}" 4 | 5 | mkdir -p "${EIB_MANIFESTDIR}" 6 | 7 | # Add basic image info to the manifest 8 | builder_commit=$(git rev-parse HEAD) 9 | cat > "${EIB_MANIFESTDIR}"/basic-info.json < "$(eib_outfile manifest.json)" 27 | -------------------------------------------------------------------------------- /stages/eib_ostree: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | . "${EIB_BASELIB}" 4 | 5 | # Pull in the latest commit. 6 | eib_retry ostree --repo="${EIB_OSTREE_REPODIR}" pull \ 7 | ${EIB_OSTREE_REMOTE} ${EIB_OSTREE_REF} 8 | 9 | # Recreate the ref locally so that the deploy can pull from it 10 | ostree --repo="${EIB_OSTREE_REPODIR}" refs --delete ${EIB_OSTREE_REF} 11 | ostree --repo="${EIB_OSTREE_REPODIR}" refs --create=${EIB_OSTREE_REF} \ 12 | ${EIB_OSTREE_REMOTE}:${EIB_OSTREE_REF} 13 | 14 | # Update the major version ref if necessary. This will be used in the 15 | # deployment instead of the minor version ref. 16 | if [ "${EIB_OSTREE_REF_DEPLOY}" != "${EIB_OSTREE_REF}" ]; then 17 | ostree_write_refs "${EIB_OSTREE_REPODIR}" ${EIB_OSTREE_REF} \ 18 | ${EIB_OSTREE_REF_DEPLOY} 19 | fi 20 | 21 | # Regenerate the summary file locally since pull --mirror will copy in 22 | # the upstream summary file, which is inaccurate since only one ref was 23 | # pulled and another ref was created locally. 24 | ostree --repo="${EIB_OSTREE_REPODIR}" summary -u 25 | -------------------------------------------------------------------------------- /stages/eib_publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # -*- mode: Shell-script; sh-basic-offset: 2; indent-tabs-mode: nil -*- 3 | . "${EIB_BASELIB}" 4 | 5 | # Don't publish for a dry run. 6 | [ "${EIB_DRY_RUN}" = true ] && exit 0 7 | 8 | run_hooks publish 9 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # pytest fixtures 2 | # https://docs.pytest.org/en/stable/fixture.html 3 | 4 | import datetime 5 | import os 6 | import pytest 7 | import shutil 8 | import subprocess 9 | import sys 10 | 11 | from .util import LIBDIR, SRCDIR, TESTSDIR, import_script 12 | 13 | run_build = import_script('run_build', os.path.join(SRCDIR, 'run-build')) 14 | 15 | # Add the lib directory to the path so eib can be imported here and in 16 | # other test modules. 17 | sys.path.insert(1, LIBDIR) 18 | import eib # noqa: E402 19 | 20 | 21 | @pytest.fixture 22 | def config(): 23 | """Provide an ImageConfigParser instance""" 24 | return eib.ImageConfigParser() 25 | 26 | 27 | @pytest.fixture 28 | def builder_config(config): 29 | """Provide an ImageBuilder config instance 30 | 31 | This fills in the build section like ImageBuilder so that 32 | interpolation of full sections should succeed. 33 | """ 34 | config[config.BUILD_SECTION].update({ 35 | 'product': 'eoscustom', 36 | 'branch': 'master', 37 | 'arch': 'amd64', 38 | 'platform': 'amd64', 39 | 'personality': 'base', 40 | 'dry_run': 'false', 41 | 'series': 'master', 42 | 'srcdir': SRCDIR, 43 | 'cachedir': eib.CACHEDIR, 44 | 'sysconfdir': eib.SYSCONFDIR, 45 | 'build_version': '200101-000000', 46 | 'use_production_ostree': 'false' 47 | }) 48 | 49 | # Make sure only the intended settings from ImageBuilder are set 50 | test_attrs = set(config[config.BUILD_SECTION]) 51 | expected_attrs = set(run_build.ImageBuilder.CONFIG_ATTRS) 52 | assert test_attrs == expected_attrs 53 | 54 | return config 55 | 56 | 57 | @pytest.fixture(scope='session') 58 | def tmp_builder_config(tmp_path_factory): 59 | """Copy the config directory avoiding unwanted local files""" 60 | configdir = tmp_path_factory.getbasetemp() / 'config' 61 | shutil.copytree(os.path.join(SRCDIR, 'config'), configdir) 62 | for child in ('local.ini', 'private.ini'): 63 | path = configdir / child 64 | if path.exists(): 65 | path.unlink() 66 | return configdir 67 | 68 | 69 | @pytest.fixture 70 | def tmp_builder_paths(tmp_path, monkeypatch): 71 | """Override image builder system paths""" 72 | paths = {} 73 | 74 | for syspath in ('cachedir', 'builddir', 'sysconfdir'): 75 | path = tmp_path / syspath 76 | path.mkdir() 77 | paths[syspath.upper()] = path 78 | monkeypatch.setattr(eib, syspath.upper(), str(path)) 79 | 80 | return paths 81 | 82 | 83 | @pytest.fixture 84 | def tmp_bindir(tmp_path, monkeypatch): 85 | """Add a temporary bin directory to the beginning of PATH""" 86 | bindir = tmp_path / 'bin' 87 | bindir.mkdir() 88 | monkeypatch.setenv('PATH', str(bindir), prepend=os.pathsep) 89 | return bindir 90 | 91 | 92 | @pytest.fixture 93 | def mock_datetime(monkeypatch): 94 | """Mock datetime class with fixed utcnow""" 95 | class MockDatetime(datetime.datetime): 96 | @classmethod 97 | def utcnow(cls): 98 | return cls(2000, 1, 1) 99 | 100 | monkeypatch.setattr(datetime, 'datetime', MockDatetime) 101 | 102 | 103 | @pytest.fixture 104 | def make_builder(tmp_builder_config, tmp_builder_paths, mock_datetime): 105 | """Factory to create ImageBuilder with defaults for required arguments""" 106 | def _make_builder(**kwargs): 107 | kwargs.setdefault('product', 'eoscustom') 108 | kwargs.setdefault('branch', 'master') 109 | kwargs.setdefault('arch', 'amd64') 110 | kwargs.setdefault('platform', 'amd64') 111 | kwargs.setdefault('personality', 'base') 112 | kwargs.setdefault('configdir', str(tmp_builder_config)) 113 | builder = run_build.ImageBuilder(**kwargs) 114 | return builder 115 | return _make_builder 116 | 117 | 118 | @pytest.fixture 119 | def builder_gpgdir(tmp_builder_paths): 120 | """Image builder GPG homedir with keys imported""" 121 | homedir = tmp_builder_paths['SYSCONFDIR'] / 'gnupg' 122 | homedir.mkdir(mode=0o700, parents=True) 123 | for key in ('test1.key', 'test2.key', 'test3.key'): 124 | key_path = os.path.join(TESTSDIR, 'data', key) 125 | subprocess.check_call(( 126 | 'gpg', '--homedir', str(homedir), '--batch', '--import', key_path 127 | )) 128 | return homedir 129 | -------------------------------------------------------------------------------- /tests/data/flatpak/app-1-locale/files/en/share/locale/en/LC_MESSAGES/app-1.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/app-1-locale/files/en/share/locale/en/LC_MESSAGES/app-1.mo -------------------------------------------------------------------------------- /tests/data/flatpak/app-1-locale/files/es/share/locale/es/LC_MESSAGES/app-1.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/app-1-locale/files/es/share/locale/es/LC_MESSAGES/app-1.mo -------------------------------------------------------------------------------- /tests/data/flatpak/app-1-locale/files/fr/share/locale/fr/LC_MESSAGES/app-1.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/app-1-locale/files/fr/share/locale/fr/LC_MESSAGES/app-1.mo -------------------------------------------------------------------------------- /tests/data/flatpak/app-1-locale/metadata: -------------------------------------------------------------------------------- 1 | [Runtime] 2 | name = com.example.App1.Locale 3 | 4 | [ExtensionOf] 5 | ref = app/com.example.App1/x86_64/master 6 | -------------------------------------------------------------------------------- /tests/data/flatpak/app-1/metadata: -------------------------------------------------------------------------------- 1 | [Application] 2 | name=com.example.App1 3 | runtime=com.example.Platform/x86_64/1 4 | sdk=com.example.Sdk/x86_64/1 5 | 6 | [Extension com.example.App1.Locale] 7 | directory=share/runtime/locale 8 | autodelete=true 9 | locale-subset=true 10 | -------------------------------------------------------------------------------- /tests/data/flatpak/app-2-locale/files/en/share/locale/en/LC_MESSAGES/app-2.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/app-2-locale/files/en/share/locale/en/LC_MESSAGES/app-2.mo -------------------------------------------------------------------------------- /tests/data/flatpak/app-2-locale/files/es/share/locale/es/LC_MESSAGES/app-2.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/app-2-locale/files/es/share/locale/es/LC_MESSAGES/app-2.mo -------------------------------------------------------------------------------- /tests/data/flatpak/app-2-locale/files/fr/share/locale/fr/LC_MESSAGES/app-2.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/app-2-locale/files/fr/share/locale/fr/LC_MESSAGES/app-2.mo -------------------------------------------------------------------------------- /tests/data/flatpak/app-2-locale/metadata: -------------------------------------------------------------------------------- 1 | [Runtime] 2 | name = com.example.App2.Locale 3 | 4 | [ExtensionOf] 5 | ref = app/com.example.App2/x86_64/master 6 | -------------------------------------------------------------------------------- /tests/data/flatpak/app-2/metadata: -------------------------------------------------------------------------------- 1 | [Application] 2 | name=com.example.App2 3 | runtime=com.example.Platform/x86_64/2 4 | sdk=com.example.Sdk/x86_64/2 5 | 6 | [Extension com.example.App2.Locale] 7 | directory=share/runtime/locale 8 | autodelete=true 9 | locale-subset=true 10 | -------------------------------------------------------------------------------- /tests/data/flatpak/app-extra-data/metadata: -------------------------------------------------------------------------------- 1 | [Application] 2 | name=com.example.AppExtraData 3 | runtime=com.example.Platform/x86_64/1 4 | sdk=com.example.Sdk/x86_64/1 5 | 6 | [Extra Data] 7 | uri = http://example.com/foo.tar.gz 8 | size = 1 9 | checksum = 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 10 | -------------------------------------------------------------------------------- /tests/data/flatpak/platform-1-locale/files/en/share/locale/en/LC_MESSAGES/platform-1.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/platform-1-locale/files/en/share/locale/en/LC_MESSAGES/platform-1.mo -------------------------------------------------------------------------------- /tests/data/flatpak/platform-1-locale/files/es/share/locale/es/LC_MESSAGES/platform-1.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/platform-1-locale/files/es/share/locale/es/LC_MESSAGES/platform-1.mo -------------------------------------------------------------------------------- /tests/data/flatpak/platform-1-locale/files/fr/share/locale/fr/LC_MESSAGES/platform-1.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/platform-1-locale/files/fr/share/locale/fr/LC_MESSAGES/platform-1.mo -------------------------------------------------------------------------------- /tests/data/flatpak/platform-1-locale/metadata: -------------------------------------------------------------------------------- 1 | [Runtime] 2 | name = com.example.Platform.Locale 3 | 4 | [ExtensionOf] 5 | ref = runtime/com.example.Platform/x86_64/1 6 | -------------------------------------------------------------------------------- /tests/data/flatpak/platform-1/metadata: -------------------------------------------------------------------------------- 1 | [Runtime] 2 | name=com.example.Platform 3 | runtime=com.example.Platform/x86_64/1 4 | sdk=com.example.Sdk/x86_64/1 5 | 6 | [Extension com.example.Platform.Locale] 7 | directory=share/runtime/locale 8 | autodelete=true 9 | locale-subset=true 10 | -------------------------------------------------------------------------------- /tests/data/flatpak/platform-2-locale/files/en/share/locale/en/LC_MESSAGES/platform-2.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/platform-2-locale/files/en/share/locale/en/LC_MESSAGES/platform-2.mo -------------------------------------------------------------------------------- /tests/data/flatpak/platform-2-locale/files/es/share/locale/es/LC_MESSAGES/platform-2.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/platform-2-locale/files/es/share/locale/es/LC_MESSAGES/platform-2.mo -------------------------------------------------------------------------------- /tests/data/flatpak/platform-2-locale/files/fr/share/locale/fr/LC_MESSAGES/platform-2.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/data/flatpak/platform-2-locale/files/fr/share/locale/fr/LC_MESSAGES/platform-2.mo -------------------------------------------------------------------------------- /tests/data/flatpak/platform-2-locale/metadata: -------------------------------------------------------------------------------- 1 | [Runtime] 2 | name = com.example.Platform.Locale 3 | 4 | [ExtensionOf] 5 | ref = runtime/com.example.Platform/x86_64/2 6 | -------------------------------------------------------------------------------- /tests/data/flatpak/platform-2/metadata: -------------------------------------------------------------------------------- 1 | [Runtime] 2 | name=com.example.Platform 3 | runtime=com.example.Platform/x86_64/2 4 | sdk=com.example.Sdk/x86_64/2 5 | 6 | [Extension com.example.Platform.Locale] 7 | directory=share/runtime/locale 8 | autodelete=true 9 | locale-subset=true 10 | -------------------------------------------------------------------------------- /tests/data/test1.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQGNBF/SbXABDADJHzIeyk+90t7Zbhy/CejkNj1BN7jPPIxBQoPk8FfyTQsUeVsc 4 | NJQeJ448VY8HQQVW3ebCxBWg5yO7Lmu1XSVyF37d7glBd2eF6bB6wbuB5YRKN/gK 5 | qFk4pO/ZK1m62CFz0UIz1NyRZCcxB26ORHuN+zR03tcdNsjBAWDV8vsGg7Bq3J8x 6 | 9MdeVPwRmEkD4Lm8vpaus5dwYT/TbBgYyVDHAYav24oNEQ1nM0CKOCyCzqln+nAE 7 | sIcZ/DaUGHhnbO+EeqQo+MOI04nFRKx/DK+AszdlPLW6YwpOIQCGLR+M+SWuiGq1 8 | 5fdsLFCv3FgUhcdgQGqlOs3RIx5QhQFHxIlaRabtuoDH8Mxkf0XcdZTR5Dw2Sjcp 9 | fCh/kV8qX3yS1FYhof1nCRluBEZ3D2heYv0adA2vthytBernvZVG70pvh7la006X 10 | SOzusic16ZR/WdLi77kXHEYsbo/l6NtoQ+b6p0mPqMbH/r3jEFuYZJgcTLKYmW9u 11 | 5de/rfts6PUqgI0AEQEAAbQZVGVzdDEgPHRlc3QxQGV4YW1wbGUuY29tPokBzgQT 12 | AQoAOBYhBLbZr1FAOBISuzSavFSSxfZ3E55CBQJf0m1wAhsDBQsJCAcCBhUKCQgL 13 | AgQWAgMBAh4BAheAAAoJEFSSxfZ3E55Cj9IMAIz0RmtbwjWqG71hFyLOTzLH4Dph 14 | 1UEVbfZIn3CjDkUa1XKPHciFTjgbfaOKAagt+mio2CA8QdjUvCg3DQEbCsduI5rr 15 | 7SxwN52G/9oboRAvPLqSEEMhK9a/1Qegnh/CYbcnQLu+640Ouzb+QGhM1w798kM8 16 | Eec8oouiTkPuFOTvjQyzNjYhon/U0G6hxW3Vj0h5Hr8SMh37Ohy8PesgxyTI5/bg 17 | 0RxdlTkkq5LHi08Qh9nkLmmysnM3Fx8mDz5lUVeZ+rEW8G0mkiXn7J4BWAWN0I1u 18 | vfXhO0zRLlPbhZVTIPPDSAfskfCS/tUP/ZZ8Ir4s95oPzUpWXlDjRIU1XEC6G3pM 19 | Nj2c8jhoINSYpBxGr16uDcM7JdpbBareytHBjd1RR50IWXFq/3En01TbZxdCBibF 20 | juGJcR6deFCtWyKVBxxb9A8Fxgk0xsKdeKqw8yCwBa18W8cAjiKncLAQey2mVsEz 21 | 4TfW28kUw/ZnZTWGPfQRj0XgERSWB5ATzA9/ng== 22 | =EdNt 23 | -----END PGP PUBLIC KEY BLOCK----- 24 | -------------------------------------------------------------------------------- /tests/data/test1.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lQVYBF/SbXABDADJHzIeyk+90t7Zbhy/CejkNj1BN7jPPIxBQoPk8FfyTQsUeVsc 4 | NJQeJ448VY8HQQVW3ebCxBWg5yO7Lmu1XSVyF37d7glBd2eF6bB6wbuB5YRKN/gK 5 | qFk4pO/ZK1m62CFz0UIz1NyRZCcxB26ORHuN+zR03tcdNsjBAWDV8vsGg7Bq3J8x 6 | 9MdeVPwRmEkD4Lm8vpaus5dwYT/TbBgYyVDHAYav24oNEQ1nM0CKOCyCzqln+nAE 7 | sIcZ/DaUGHhnbO+EeqQo+MOI04nFRKx/DK+AszdlPLW6YwpOIQCGLR+M+SWuiGq1 8 | 5fdsLFCv3FgUhcdgQGqlOs3RIx5QhQFHxIlaRabtuoDH8Mxkf0XcdZTR5Dw2Sjcp 9 | fCh/kV8qX3yS1FYhof1nCRluBEZ3D2heYv0adA2vthytBernvZVG70pvh7la006X 10 | SOzusic16ZR/WdLi77kXHEYsbo/l6NtoQ+b6p0mPqMbH/r3jEFuYZJgcTLKYmW9u 11 | 5de/rfts6PUqgI0AEQEAAQAL/jJWh4HjWxAEvlgnUjSSc2LjHHO9UZk061nOHxrm 12 | +OwhUluegCpQEp51ggkh1PmY0ZBLW911nIA4Od44oSa0mJG0xcEgOdrT2upOcihX 13 | YuoBGxpEZeWs+x/NUGbHjd7RB5ZoUqyMY2RUb+a6mHYtN8tyi0+REsAyHQ8JnEYQ 14 | WbnorfghHgsv29KFbM2ukt/QmExgW3CAbK/o90Ogc2uIv5n/jMlpJmVc1kD1N/sg 15 | UiuvNAFdUjoqQLZFRRfOBBzGy73C1pLSEmpGzc0SGVpvshbXnuQJt0kabByvOals 16 | NPFsC4xWPuPXE/7YruQhC92yHzZIyKNIoqqitCLbsXtKFVjwVwJvmbeNh7t4qXK0 17 | +z3xwMiq1QaPxr7k+kW2xYT5gjkdTXOQwZmz05Jq4nxHhk1UFBkd3UTGn2ptXn46 18 | s5rmVrMUEBndggEF7nXUhuQq8B74Efl4SoGyPXcDk4GcSryzxYD+HsvZZ5y3A21I 19 | FQu5O3awFeqIHoI9iSsWtZ1kNQYAyjncQDwWMEZZy3iQ3MRozLUHHfJTLe3VbvSp 20 | QWORLALFw+mfgadUvv6zcaPcrqnX6dqLEkv3KgpBQkIE+IlheOOPNPZbD1yWJT/g 21 | Gr93zzMfIscctHbY3yJ61hGv18FU29842foT+eR2kY4vNzeq5IMY1sFhNFICrzSv 22 | c+DfTHYade/TziSyxqc2xxvO4FUH0E87XMofvUTnTWeJbf2PQMQX5sb6HFi3ixC+ 23 | Fkt38xFGh9Ayy6klhzCjLyA6ELb7BgD+miwIYLO1RMko0owGxj80EVOqJpvgKYZ6 24 | hN+ZuN3e2Qy9yO3Jomm2ZCoZJ7xjmKfOC96a812B+mcDDv5irx7n9WQy9W56XbFK 25 | tmq+wUXm2WY0HvVqcT5a4KXkEvFFPd/BOAN655R/0qG2PW2q7CqiR0WEOglbBSdV 26 | mSFS/6guinCg9Jj8do489P0EBYYvErXJUX1oQXN8VsKPx779KkqpyfBVOCPh4R70 27 | NryTsd2BzARj4l7CozueQvPxD/8hMBcGANNqXAemkhMEl3f0lDLeZE4/JF0V4WQA 28 | vC5GAkf2od4ISTrvEBCqh8WILPbkEkzH6PtP5C7lP78mfxsl41JGn6b7Sm6vyyHL 29 | JBBBR0kI4cyV233uepTbeoy8rwV1t1mqDZF2VUThDiCkGLaWNPtStbrT+3Cc36HA 30 | gh2RYHbJ2T/WPodmgE5TawIyIngAv9AzH89doyb16mqz7lDUX36S3gbYh8QDJzyR 31 | dykTfYCjHObJiB+qQhOuJe7MOgMT+v/sNt2ytBlUZXN0MSA8dGVzdDFAZXhhbXBs 32 | ZS5jb20+iQHOBBMBCgA4FiEEttmvUUA4EhK7NJq8VJLF9ncTnkIFAl/SbXACGwMF 33 | CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQVJLF9ncTnkKP0gwAjPRGa1vCNaob 34 | vWEXIs5PMsfgOmHVQRVt9kifcKMORRrVco8dyIVOOBt9o4oBqC36aKjYIDxB2NS8 35 | KDcNARsKx24jmuvtLHA3nYb/2huhEC88upIQQyEr1r/VB6CeH8JhtydAu77rjQ67 36 | Nv5AaEzXDv3yQzwR5zyii6JOQ+4U5O+NDLM2NiGif9TQbqHFbdWPSHkevxIyHfs6 37 | HLw96yDHJMjn9uDRHF2VOSSrkseLTxCH2eQuabKyczcXHyYPPmVRV5n6sRbwbSaS 38 | JefsngFYBY3QjW699eE7TNEuU9uFlVMg88NIB+yR8JL+1Q/9lnwiviz3mg/NSlZe 39 | UONEhTVcQLobekw2PZzyOGgg1JikHEavXq4Nwzsl2lsFqt7K0cGN3VFHnQhZcWr/ 40 | cSfTVNtnF0IGJsWO4YlxHp14UK1bIpUHHFv0DwXGCTTGwp14qrDzILAFrXxbxwCO 41 | IqdwsBB7LaZWwTPhN9bbyRTD9mdlNYY99BGPReARFJYHkBPMD3+e 42 | =Yk6F 43 | -----END PGP PRIVATE KEY BLOCK----- 44 | -------------------------------------------------------------------------------- /tests/data/test2.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQGNBF/SbXEBDADOXQYHurCOWArq0MqdyDURUh9Cl/uqUb6W4FvoIwosPyuISf8J 4 | KFGCOtKrc6qOrjsylj2W/lpKRX8ksXp+FLmd7UfUyei/z4VBXeMInE6W6b1jobtM 5 | T553jcqAG9ofH6+Ug7X4megsZA4pl/v4nTXrizSu3MxrxYDL7/VJ7bMBeZ0W2nUd 6 | sxOiPptOcxj4Da5qVx/OaWPU+GKmneyqjjw8qLgjkNBMkhxpLTn8BJgsP1J9pxEc 7 | V/zRNY4Q+TkN77lmuSD6X+SiG13xdPyDwcIubjucner5IydHrV4Rrfd0SAolkXvN 8 | E7iIyiZesFySNLCnZctazCRpEbPqOvMoioa4nK+k3n/NFX9PiR+O/ZhYqAgYfUsG 9 | H1INSQhb3kRfdoP9E9kKqr5gQL9bcd59kVX5F0gouMRbcNH1yUYp/uk2FjI5WZnT 10 | BE8/IjcJeoBn60H29SE5B5m4axxTWnmmR/dj2Z0XJwkxvKvlL8Avkd/y585IA0uS 11 | Ye9KK21pRFQoJCsAEQEAAbQZVGVzdDIgPHRlc3QyQGV4YW1wbGUuY29tPokBzgQT 12 | AQoAOBYhBNtyTsJrs9RmidSOFyks36nuEVa1BQJf0m1xAhsDBQsJCAcCBhUKCQgL 13 | AgQWAgMBAh4BAheAAAoJECks36nuEVa1cJIL/3UV8jjN1IQGW0K/mb4mvE+mfhRp 14 | fuThP6hjLBffCiIKFMVxRLnPMegDREE/2xhUp3O1x3IYUU2OLZkkZQknrXDJYX0M 15 | 7aRLe3QinwridT+QCdZz+WT7dbR7fpyjVDXB3gXrEw2G8HFBvb8Hz/7d5hCJh9mj 16 | 9SBuKxEgj4pWPy1C4oFHmd/Bzh/DxLsmB7ZICwPvB4srdv119dTtvX0xRLlX3KP4 17 | +7mbi2HzbstSh8HMgtRxd1kkKDJEZ8khL7KurrVDBKL/MDlsPE2JMx/AS8Gdm22N 18 | lmn5UX71v+DAq0157n5UHCCfP0omSUU6nzYnLPtPl7n68rqVIbDsAQb5FxcTxSF1 19 | DAJJ0ZbqBg2Mdb+4lp91MaADFjlZGF7XdAzCM2DG89FGy5RDEJ9VyFTldE8J82Eb 20 | R464wo+J5KDMix8pyseCCzAKHlzPCRR9Uqf7+uWzXbsR/ZwDdeB3xxbwiWc1E3Je 21 | KzZgXGk+uBY4n80RjJv7kl13nAqzNx64iOf6Jg== 22 | =zryA 23 | -----END PGP PUBLIC KEY BLOCK----- 24 | -------------------------------------------------------------------------------- /tests/data/test2.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lQVYBF/SbXEBDADOXQYHurCOWArq0MqdyDURUh9Cl/uqUb6W4FvoIwosPyuISf8J 4 | KFGCOtKrc6qOrjsylj2W/lpKRX8ksXp+FLmd7UfUyei/z4VBXeMInE6W6b1jobtM 5 | T553jcqAG9ofH6+Ug7X4megsZA4pl/v4nTXrizSu3MxrxYDL7/VJ7bMBeZ0W2nUd 6 | sxOiPptOcxj4Da5qVx/OaWPU+GKmneyqjjw8qLgjkNBMkhxpLTn8BJgsP1J9pxEc 7 | V/zRNY4Q+TkN77lmuSD6X+SiG13xdPyDwcIubjucner5IydHrV4Rrfd0SAolkXvN 8 | E7iIyiZesFySNLCnZctazCRpEbPqOvMoioa4nK+k3n/NFX9PiR+O/ZhYqAgYfUsG 9 | H1INSQhb3kRfdoP9E9kKqr5gQL9bcd59kVX5F0gouMRbcNH1yUYp/uk2FjI5WZnT 10 | BE8/IjcJeoBn60H29SE5B5m4axxTWnmmR/dj2Z0XJwkxvKvlL8Avkd/y585IA0uS 11 | Ye9KK21pRFQoJCsAEQEAAQAL/1pvCaV5esXmb2JX/XMHubnNc0WLtQ3+pLcZuy0P 12 | rfWV+U3Qn6MzuASKIqhgF+5PDEIu9O6mqIrDyeQaosN98Znz0fa+RPFJz+vFaSjs 13 | 1sPrysNOrPXULm3WBVf+3KenCxraXXSEfep2Ln6rzBA3VNnUN1IqEKtp8fdtQOEU 14 | +c07rkvFkG1jfMctCIJojhimZjjT1WxvYwCBmTnLG1vi2SL8DTYI0D++CBVPbIm0 15 | ubsqRMwr2qPBK1nDTm7ByO8CjkLUXQmLpS/6YKm5JF4EyULe/9jUmJziaTbfrOSZ 16 | kihSKwtG2gcbRdK0r29ftdqnCAaniGNu8Qj5Gf984Ub3tPLpA++RF1pRqc5lgcaZ 17 | Gvuy/cmO2+2PGzt+SuYTxxJ6O4gsVIznHryObKXLhECyAEa/xbQXuVJbPlTjBpLB 18 | KpnQ8buWWxiL6bi6EV3opJTHX+sFCZThX9Il7xikPVP0b8zViVX5eSCcHyQSiCUE 19 | mcYvJa5GBzf73Ue0xPjq6umkAQYA0Xwm9zZvm7TWDLcNfpQPp/2QxGj18RPXooA+ 20 | VkiqlPG5cLDODQSpUSBth82C2s0n5x6yECldbTLkfPzVtqhDx5OuwbQZKNYD/G+J 21 | pW/xUcXwJhd6BR0I2e4m7JFIvBq56OKyrEgnB/reT/HMnul3FjBMa5CYYH52EBEl 22 | HbmVnZycHcSVql2zUQSQ9xeaXf8ogkylcJ5sCaNZ7AiUOfIOvtY6Vym92BC+7JB0 23 | g7tx4M+ED7JGN26YS1OU74QTuIqLBgD8L23WTWiDew/qN3Yom+BhSSNJjIMQ8k4i 24 | 4u3HCGU+SE8ElZ05hb7MdoDBErk/6wEkQdSYJ6hkmsE7H015E8lWBvIfBUIsFTrc 25 | ThFP2zkGeS85c5n8NERB0TJnLup32SsuOJhhj7KNWjEEJ2oKq0jqmm3MlSfBJUSB 26 | u06iYQShNuFw8DuBJ4QSTp6Vpgy2GUSBTpfKT1KW6Wf/CUAOMRRqxwDPpwSqD8LB 27 | IPgsFJ9SDQ9Xcg82sq/QndRcOt6PIOEF/jxFXotco95Cypv6zifK+o7BG2lTaQEI 28 | Ir8R4RmzyTQZxBhjAU2iI/P3ViYyt2w1MhneoSVf8qEUmQN8pJuDQFeigUOVmbdf 29 | GE1+6W6IYb+Ogv1ffD4qP3KNalX/z4M3Qv5rGIZ8EBjzvxC3aTV/KMLC4VDjkdxp 30 | 4998IThVUEMA+r1oVkMheaBsI1DMkmsD9KsRhNh7hTcybnW31agHNe3qjU/I/QQR 31 | Kx1z6oToV6kjwfqfG4rB8wvUOk9ZqfDAetqTtBlUZXN0MiA8dGVzdDJAZXhhbXBs 32 | ZS5jb20+iQHOBBMBCgA4FiEE23JOwmuz1GaJ1I4XKSzfqe4RVrUFAl/SbXECGwMF 33 | CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQKSzfqe4RVrVwkgv/dRXyOM3UhAZb 34 | Qr+Zvia8T6Z+FGl+5OE/qGMsF98KIgoUxXFEuc8x6ANEQT/bGFSnc7XHchhRTY4t 35 | mSRlCSetcMlhfQztpEt7dCKfCuJ1P5AJ1nP5ZPt1tHt+nKNUNcHeBesTDYbwcUG9 36 | vwfP/t3mEImH2aP1IG4rESCPilY/LULigUeZ38HOH8PEuyYHtkgLA+8Hiyt2/XX1 37 | 1O29fTFEuVfco/j7uZuLYfNuy1KHwcyC1HF3WSQoMkRnySEvsq6utUMEov8wOWw8 38 | TYkzH8BLwZ2bbY2WaflRfvW/4MCrTXnuflQcIJ8/SiZJRTqfNics+0+XufryupUh 39 | sOwBBvkXFxPFIXUMAknRluoGDYx1v7iWn3UxoAMWOVkYXtd0DMIzYMbz0UbLlEMQ 40 | n1XIVOV0TwnzYRtHjrjCj4nkoMyLHynKx4ILMAoeXM8JFH1Sp/v65bNduxH9nAN1 41 | 4HfHFvCJZzUTcl4rNmBcaT64FjifzRGMm/uSXXecCrM3HriI5/om 42 | =TOd5 43 | -----END PGP PRIVATE KEY BLOCK----- 44 | -------------------------------------------------------------------------------- /tests/data/test3.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQGNBF/SbXIBDAC9IbGNvunZvV2J9a3o0xot5GQTGtIJt5tidqrTVEmuOM9lnm4v 4 | V5zoR0v9t1XHbF8kLiTbqEfXAPsFFmMFxmGD/uGqqUQWnTZYqxJIAphpkYiHAJnd 5 | TIZGnPw5Sm1mdzV0UhFQx5JaID7Hq4rWrBDqYhzKpxxloMOLxNSOQ8VGIBLKowzC 6 | YsePZiRxAzbxGQ32mBFkiiLnfUOjyPOJlqVh0NrtyO1ATy4AaxWWYmi40Tksy7xu 7 | QKTLMaWqzQeiO9al/+SXm9EmmRjM+OxDCXynEDuZQxOVsGmN9rq6Nr3v+jq3Y0nE 8 | j0SBQAvZ1F0OKHAnAuK2H/qNW5YMw2hBaX/xzR+aEeburYD9oqhQqUhEZCEOCGM5 9 | z73fYArRoDlz5bCj7IVXDIMlnicHZ0fRN64v9O3HbAoEYxyV0diaEpO0NKtZcLfr 10 | NyvEsoRv8IkdCTxfZdYtYfX/FVZsjgRUQvi6UHQ8T5ni4eChFJv5YxZu4JNDjNsp 11 | ifg4wFICFnrv9e0AEQEAAbQZVGVzdDMgPHRlc3QzQGV4YW1wbGUuY29tPokBzgQT 12 | AQoAOBYhBH9C8/zVFMliAVcFTEH8E8RHA6/wBQJf0m1yAhsDBQsJCAcCBhUKCQgL 13 | AgQWAgMBAh4BAheAAAoJEEH8E8RHA6/wFG0L/2mJ//HQ60xzGXP94VGU+Cg2g3fx 14 | r4L58qBZX6hBVVAUcjgVaNhanIXJCjB1FIYYz+abtPU3FsdKJoGNM2wNeJucHyRB 15 | NLFziMO7M0bta6zZPuipqbSkbhxRMF4byGzgnnnDYtqPLnsSnv5plyuAR6EhZtWz 16 | YBAC68whGoJa4lpm30XEp/sX0zouPReeBzdWnv8qWX/tSQB5SZKuH43TBz+dVkgg 17 | 9/JvwxmFrGMI9oPkz/27dk9CS0/o4YGCG/GLG75JKo15avm42ySa0Nw20WRKZscG 18 | 453rwaKzr8XBkdxH3k0nF7gX2D1k9IELkqIEQlzTfGGz4IOrHa909Rip9lZ/I1qZ 19 | sMyFLC28Bh+HTvwk5KV+B/QvSk1rTo4JMwrC+rwq8MWvEDk+Sv/7rfHw+Uptnq05 20 | nw3WzX4CTPFKGwumaW2nQGvXSmxtXgFiXx6RwZL7xMoskiq4YozsfkcqdBvAyl88 21 | v0Lv0/Uug33rBpYT1mQl/gbVLHOkqGdKuYdvRg== 22 | =6gq9 23 | -----END PGP PUBLIC KEY BLOCK----- 24 | -------------------------------------------------------------------------------- /tests/data/test3.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lQVYBF/SbXIBDAC9IbGNvunZvV2J9a3o0xot5GQTGtIJt5tidqrTVEmuOM9lnm4v 4 | V5zoR0v9t1XHbF8kLiTbqEfXAPsFFmMFxmGD/uGqqUQWnTZYqxJIAphpkYiHAJnd 5 | TIZGnPw5Sm1mdzV0UhFQx5JaID7Hq4rWrBDqYhzKpxxloMOLxNSOQ8VGIBLKowzC 6 | YsePZiRxAzbxGQ32mBFkiiLnfUOjyPOJlqVh0NrtyO1ATy4AaxWWYmi40Tksy7xu 7 | QKTLMaWqzQeiO9al/+SXm9EmmRjM+OxDCXynEDuZQxOVsGmN9rq6Nr3v+jq3Y0nE 8 | j0SBQAvZ1F0OKHAnAuK2H/qNW5YMw2hBaX/xzR+aEeburYD9oqhQqUhEZCEOCGM5 9 | z73fYArRoDlz5bCj7IVXDIMlnicHZ0fRN64v9O3HbAoEYxyV0diaEpO0NKtZcLfr 10 | NyvEsoRv8IkdCTxfZdYtYfX/FVZsjgRUQvi6UHQ8T5ni4eChFJv5YxZu4JNDjNsp 11 | ifg4wFICFnrv9e0AEQEAAQAL/RwVG6sDVO+6uV/ZtdfjOHjl96IKknNU6uKE4jp8 12 | wm/3CXx6OLwY0zh+FZ93kGoLAll57IkdBIW9JIiN2WgrG6Ggv9s6RNzivQYPaKh+ 13 | kijirhX1leMREML9XkoXbgUP4EUERoPrDiUJsqitJPFZHS3gIYxJoeLJKx/euvzg 14 | vTL7Yi7qZOOcDSuSrAiHxKmp4bPiwnpeNG6Q3/E6zkZ4stfCTi+ObQPmfdscC1mI 15 | BSqkDkxDeBgayx1Mot+oj+Cb57LbQBfcPllXv5+HudX4nTcoDFpr8d0S9AZi33Rm 16 | jXZt8uEQGnWK5cXQLCPzLvPwmveyQghsv1Xe/opsRUCc4CnE9wgn3u3/xyPnq03x 17 | VO0P2uAPjK0RABHQLYsmd9rUXeNPxBfPpLqX4RgqVIFhjxNX24mpGQFevKpEWrBc 18 | PdoeJ37Zdsg6L80vt6hyNcQOtCYXAeQ6GcTG5uSLOTI40CqGvMvqU2pdpqHf+F13 19 | Vlgrhhm17hsI9HbRbbXferXGoQYA05wO9ZRQdOGb1IxqDQYpYXMBb2mjqQ01P/0U 20 | 8yBkeHi11zcXOMxjM5+m7VHLL1WZW3c9usE/rL8vftW+CtbnOkYk2had9gSc9UG0 21 | P6Ez/6dDxQUYoNu9EfnjkmOA5rG7x8rbfoTOZR/yuSYw0yIWRWE1F+OyVUTmTYkm 22 | K4005eDpr0/OiQSpzlvynho41qE/152X5KuTvOJX+OKk3Kf7qjxUdwKLMiJgQdQB 23 | i4/FBfA/8mwccXUFAxcYY3ClOgJ1BgDkzoNx4J1NMUspouc9jSwvTF7MNmap35qo 24 | qRJDasz7+watbPQFm+j+IE2qJ0+f2slRUGgdQpZLHz6AzknTLcqbCF2p4rzcLRwG 25 | GE/4Li0XtGvZReIPlT1DJUul8SpFlm79E+uS0lpg+ukyMvikHEN4o+deQF+d3fED 26 | ntRZjjr95CtlH+U1cuG9GvXjyE/zyalTHDELjBuFPWTok+MSouWdHdl1nKEghxJQ 27 | MEKGCU5sfmf2Vt1eUYYDLe7zWoAmxpkGAOQsIGE7daP7Uh47He8GKzN1EkmOEtY2 28 | vN9Wog4qrjZKXOaF2JNpOsVd7/nOVKDBJ+qtG6MdB6xsKSP7ZzI+8Zca48awrh5i 29 | euh6mHoLKLJm1JXLU4GWXb76xYrcl852Bf+f/N8zHXZE+rLevEdcne67nNsFcOAT 30 | JtPf487DYnolbcKObXD1ukZiNwzsgsH4FL1YdBosTDnrN48IJ7SK3Znn4UiAhssr 31 | XwZ4LFJv9Z5OnaDuac/wGI0uamkn80jY1eBwtBlUZXN0MyA8dGVzdDNAZXhhbXBs 32 | ZS5jb20+iQHOBBMBCgA4FiEEf0Lz/NUUyWIBVwVMQfwTxEcDr/AFAl/SbXICGwMF 33 | CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQQfwTxEcDr/AUbQv/aYn/8dDrTHMZ 34 | c/3hUZT4KDaDd/GvgvnyoFlfqEFVUBRyOBVo2FqchckKMHUUhhjP5pu09TcWx0om 35 | gY0zbA14m5wfJEE0sXOIw7szRu1rrNk+6KmptKRuHFEwXhvIbOCeecNi2o8uexKe 36 | /mmXK4BHoSFm1bNgEALrzCEaglriWmbfRcSn+xfTOi49F54HN1ae/ypZf+1JAHlJ 37 | kq4fjdMHP51WSCD38m/DGYWsYwj2g+TP/bt2T0JLT+jhgYIb8YsbvkkqjXlq+bjb 38 | JJrQ3DbRZEpmxwbjnevBorOvxcGR3EfeTScXuBfYPWT0gQuSogRCXNN8YbPgg6sd 39 | r3T1GKn2Vn8jWpmwzIUsLbwGH4dO/CTkpX4H9C9KTWtOjgkzCsL6vCrwxa8QOT5K 40 | //ut8fD5Sm2erTmfDdbNfgJM8UobC6ZpbadAa9dKbG1eAWJfHpHBkvvEyiySKrhi 41 | jOx+Ryp0G8DKXzy/Qu/T9S6DfesGlhPWZCX+BtUsc6SoZ0q5h29G 42 | =9EVp 43 | -----END PGP PRIVATE KEY BLOCK----- 44 | -------------------------------------------------------------------------------- /tests/eib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endlessm/eos-image-builder/dc9ff8104c52cabcafbe740daab315a106aa7039/tests/eib/__init__.py -------------------------------------------------------------------------------- /tests/eib/test_eibostree.py: -------------------------------------------------------------------------------- 1 | # Tests for eibostree module 2 | 3 | import logging 4 | import pytest 5 | import shutil 6 | 7 | from ..util import http_server_thread, run_command 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | HAVE_PREREQS = True 13 | try: 14 | import gi 15 | except ImportError: 16 | logger.debug('Missing python gi library') 17 | HAVE_PREREQS = False 18 | 19 | if HAVE_PREREQS: 20 | try: 21 | gi.require_version('OSTree', '1.0') 22 | # Only used in eibostree 23 | from gi.repository import Gio, GLib, OSTree # noqa: F401 24 | except ValueError as err: 25 | logger.debug('Missing OSTree GI bindings: %s', err) 26 | HAVE_PREREQS = False 27 | 28 | if HAVE_PREREQS: 29 | if not shutil.which('ostree'): 30 | logger.debug('Missing ostree CLI program') 31 | HAVE_PREREQS = False 32 | 33 | if not HAVE_PREREQS: 34 | pytest.skip('Missing eibostree prerequisites', allow_module_level=True) 35 | 36 | import eibostree # noqa: E402 37 | 38 | 39 | @pytest.fixture 40 | def local_ostree_repo_path(tmp_path): 41 | path = tmp_path / 'local-ostree-repo' 42 | path.mkdir() 43 | return path 44 | 45 | 46 | @pytest.fixture 47 | def local_ostree_repo(local_ostree_repo_path): 48 | repo_file = Gio.File.new_for_path(str(local_ostree_repo_path)) 49 | repo = OSTree.Repo.new(repo_file) 50 | repo.create(OSTree.RepoMode.ARCHIVE) 51 | return repo 52 | 53 | 54 | @pytest.fixture 55 | def remote_ostree_repo_path(tmp_path): 56 | path = tmp_path / 'remote-ostree-repo' 57 | path.mkdir() 58 | return path 59 | 60 | 61 | @pytest.fixture 62 | def remote_ostree_repo(remote_ostree_repo_path): 63 | repo_file = Gio.File.new_for_path(str(remote_ostree_repo_path)) 64 | repo = OSTree.Repo.new(repo_file) 65 | repo.set_collection_id('com.example.OSRepo') 66 | repo.create(OSTree.RepoMode.ARCHIVE) 67 | return repo 68 | 69 | 70 | @pytest.fixture 71 | def remote_ostree_server(remote_ostree_repo_path, remote_ostree_repo): 72 | with http_server_thread(remote_ostree_repo_path) as url: 73 | yield { 74 | 'path': remote_ostree_repo_path, 75 | 'repo': remote_ostree_repo, 76 | 'url': url, 77 | } 78 | 79 | 80 | def test_fetch_remote_collection_id(local_ostree_repo, remote_ostree_server): 81 | """Test fetch_remote_collection_id""" 82 | local_ostree_repo.remote_add('test', remote_ostree_server['url']) 83 | 84 | remote_repo = remote_ostree_server['repo'] 85 | remote_repo.set_collection_id('com.example.Test') 86 | remote_repo.write_config( 87 | remote_repo.copy_config() 88 | ) 89 | run_command([ 90 | 'ostree', 91 | f'--repo={remote_ostree_server["path"]}', 92 | 'summary', 93 | '--update', 94 | ]) 95 | collection_id = eibostree.fetch_remote_collection_id( 96 | local_ostree_repo, 'test' 97 | ) 98 | assert collection_id == 'com.example.Test' 99 | 100 | remote_repo.set_collection_id(None) 101 | remote_repo.write_config( 102 | remote_repo.copy_config() 103 | ) 104 | run_command([ 105 | 'ostree', 106 | f'--repo={remote_ostree_server["path"]}', 107 | 'summary', 108 | '--update', 109 | ]) 110 | collection_id = eibostree.fetch_remote_collection_id( 111 | local_ostree_repo, 'test' 112 | ) 113 | assert collection_id is None 114 | -------------------------------------------------------------------------------- /tests/eib/test_image_config_parser.py: -------------------------------------------------------------------------------- 1 | # Tests for ImageConfigParser 2 | 3 | import eib 4 | import io 5 | import os 6 | import pytest 7 | from textwrap import dedent 8 | 9 | from ..util import SRCDIR 10 | 11 | 12 | def get_combined_ini(config): 13 | """Get the combined config as a string""" 14 | with io.StringIO() as buf: 15 | config.write(buf) 16 | return buf.getvalue() 17 | 18 | 19 | def test_missing(tmp_path, config): 20 | """Test reading missing config file 21 | 22 | ConfigParser succeeds if the path doesn't exist, which happens a lot 23 | because config files for all attributes of the build 24 | (product/arch/etc) are read. 25 | """ 26 | assert not config.read_config_file(tmp_path / 'missing', 'missing') 27 | assert config.sections() == ['build'] 28 | assert get_combined_ini(config) == '[build]\n\n' 29 | 30 | 31 | def test_interpolation(tmp_path, config): 32 | """Test value interpolation""" 33 | f = tmp_path / 'f.ini' 34 | f.write_text(dedent("""\ 35 | [build] 36 | a = a 37 | b = b 38 | 39 | [a] 40 | a = b 41 | c = c 42 | 43 | d = ${a} 44 | e = ${a:a} 45 | f = ${build:a} 46 | g = ${build:b} 47 | h = ${c} 48 | i = ${d} 49 | """)) 50 | assert config.read_config_file(f, 'f') 51 | 52 | sect = config['a'] 53 | assert sect['d'] == 'b' 54 | assert sect['e'] == 'b' 55 | assert sect['f'] == 'a' 56 | assert sect['g'] == 'b' 57 | assert sect['h'] == 'c' 58 | assert sect['i'] == 'b' 59 | 60 | 61 | def test_config_multiple(tmp_path, config): 62 | """Test multipile config files combined""" 63 | f1 = tmp_path / 'f1.ini' 64 | f1.write_text(dedent("""\ 65 | [a] 66 | a = a 67 | b = b 68 | """)) 69 | 70 | f2 = tmp_path / "f2.ini" 71 | f2.write_text(dedent("""\ 72 | [a] 73 | b = c 74 | c = c 75 | 76 | [b] 77 | a = a 78 | b 79 | """)) 80 | 81 | assert config.read_config_file(f1, 'f1') 82 | assert config.read_config_file(f2, 'f2') 83 | 84 | assert config.sections() == ['build', 'a', 'b'] 85 | assert config.options('a') == ['a', 'b', 'c'] 86 | assert config.options('b') == ['a'] 87 | assert config['a']['a'] == 'a' 88 | assert config['a']['b'] == 'c' 89 | assert config['a']['c'] == 'c' 90 | assert config['b']['a'] == 'a\nb' 91 | 92 | expected_combined = dedent("""\ 93 | [build] 94 | 95 | [a] 96 | a = a 97 | b = c 98 | c = c 99 | 100 | [b] 101 | a = a 102 | \tb 103 | 104 | """) 105 | assert get_combined_ini(config) == expected_combined 106 | 107 | 108 | def test_merged_option(config): 109 | """Test option merging""" 110 | config.MERGED_OPTIONS = [('sect', 'opt')] 111 | config.add_section('sect') 112 | 113 | # Standard add/del counters 114 | sect = config['sect'] 115 | sect['opt_add_1'] = 'foo bar baz' 116 | sect['opt_add_2'] = 'baz' 117 | sect['opt_del_1'] = 'bar baz' 118 | config.merge() 119 | 120 | assert set(sect) == {'opt'} 121 | 122 | # The values will be newline separated in the order they appeared. 123 | assert sect['opt'] == 'foo\nbaz' 124 | 125 | # Now that the merged option exists, it will override any further 126 | # add/del. 127 | sect['opt_add_1'] = 'bar' 128 | sect['opt_del_1'] = 'foo' 129 | config.merge() 130 | 131 | assert set(sect) == {'opt'} 132 | assert sect['opt'] == 'foo\nbaz' 133 | 134 | 135 | def test_merged_option_interpolation(config): 136 | """Test option merging with interpolation""" 137 | config.MERGED_OPTIONS = [('sect', 'opt')] 138 | config.add_section('sect') 139 | 140 | sect = config['sect'] 141 | sect['opt_add_1'] = 'foo' 142 | sect['opt_add_2'] = 'bar baz' 143 | sect['opt_del_1'] = '${opt_add_1} baz' 144 | config.merge() 145 | 146 | assert set(sect) == {'opt'} 147 | 148 | assert sect['opt'] == 'bar' 149 | 150 | 151 | def test_merged_pattern_section(config): 152 | """Test option merging in a patterned section""" 153 | config.MERGED_OPTIONS = [('sect-*', 'opt')] 154 | config.add_section('sect-a') 155 | a = config['sect-a'] 156 | config.add_section('sect-b') 157 | b = config['sect-b'] 158 | 159 | a['opt_add_test'] = 'foo\n bar' 160 | a['opt_del_test'] = 'bar' 161 | b['opt'] = 'baz' 162 | b['opt_add_test'] = 'foo' 163 | config.merge() 164 | 165 | assert 'opt_add_test' not in a 166 | assert 'opt_del_test' not in a 167 | assert a['opt'] == 'foo' 168 | assert 'opt_add_test' not in b 169 | assert b['opt'] == 'baz' 170 | 171 | 172 | def test_merged_namespace_errors(tmp_path, config): 173 | a = tmp_path / 'a.ini' 174 | b = tmp_path / 'b.ini' 175 | 176 | # Bad namespace options 177 | with pytest.raises(eib.ImageBuildError, 178 | match='namespace must be a non-empty string'): 179 | config.read_config_file(a, None) 180 | with pytest.raises(eib.ImageBuildError, 181 | match='namespace must be a non-empty string'): 182 | config.read_config_file(a, '') 183 | 184 | # Conflicting namespaces 185 | config.read_config_file(a, 'test') 186 | with pytest.raises(eib.ImageBuildError, 187 | match='namespace test is already used'): 188 | config.read_config_file(b, 'test') 189 | 190 | 191 | def test_merged_namespaces(tmp_path, config): 192 | config.MERGED_OPTIONS = [('sect', 'opt')] 193 | expected_opts = set() 194 | 195 | # Automatic namespacing 196 | a = tmp_path / 'a.ini' 197 | a.write_text(dedent("""\ 198 | [sect] 199 | opt_add = foo 200 | opt_del = bar 201 | """)) 202 | assert config.read_config_file(a, 'a') 203 | add_opt = 'opt_add_a' 204 | del_opt = 'opt_del_a' 205 | expected_opts.update({add_opt, del_opt}) 206 | assert set(config['sect']) == expected_opts 207 | assert config['sect'][add_opt] == 'foo' 208 | assert config['sect'][del_opt] == 'bar' 209 | 210 | # Configuration defined namespacing 211 | b = tmp_path / 'b.ini' 212 | b.write_text(dedent("""\ 213 | [sect] 214 | opt_add_test = bar 215 | opt_del_test = foo 216 | """)) 217 | assert config.read_config_file(b, 'b') 218 | add_opt = 'opt_add_test' 219 | del_opt = 'opt_del_test' 220 | expected_opts.update({add_opt, del_opt}) 221 | assert set(config['sect']) == expected_opts 222 | assert config['sect'][add_opt] == 'bar' 223 | assert config['sect'][del_opt] == 'foo' 224 | 225 | config.merge() 226 | assert set(config['sect']) == {'opt'} 227 | assert config['sect']['opt'] == '' 228 | 229 | 230 | def test_merged_files(tmp_path, config): 231 | """Test option merging from files""" 232 | config.MERGED_OPTIONS = [('sect', 'opt'), ('sect-*', 'opt')] 233 | a = tmp_path / 'a.ini' 234 | a.write_text(dedent("""\ 235 | [sect] 236 | opt_add = 237 | foo 238 | bar 239 | baz 240 | 241 | [sect-a] 242 | opt_add = 243 | foo 244 | bar 245 | 246 | [sect-b] 247 | opt = baz 248 | """)) 249 | 250 | b = tmp_path / 'b.ini' 251 | b.write_text(dedent("""\ 252 | [sect] 253 | opt_add = 254 | baz 255 | 256 | [sect-a] 257 | opt_del = bar 258 | 259 | [sect-b] 260 | opt_add = foo 261 | """)) 262 | 263 | c = tmp_path / 'c.ini' 264 | c.write_text(dedent("""\ 265 | [sect] 266 | opt_del = 267 | bar 268 | baz 269 | 270 | [sect-a] 271 | opt_del = foo 272 | """)) 273 | 274 | assert config.read_config_file(a, 'a') 275 | assert config.read_config_file(b, 'b') 276 | assert config.read_config_file(c, 'c') 277 | config.merge() 278 | 279 | assert set(config['sect']) == {'opt'} 280 | assert config['sect']['opt'] == 'foo\nbaz' 281 | assert set(config['sect-a']) == {'opt'} 282 | assert config['sect-a']['opt'] == '' 283 | assert set(config['sect-b']) == {'opt'} 284 | assert config['sect-b']['opt'] == 'baz' 285 | 286 | 287 | def test_defaults(builder_config): 288 | """Test defaults.ini can be loaded and resolved""" 289 | defaults = os.path.join(SRCDIR, 'config/defaults.ini') 290 | assert builder_config.read_config_file(defaults, 'defaults') 291 | for sect in builder_config: 292 | # Make sure all the values can be resolved 293 | builder_config.items(sect) 294 | 295 | 296 | def test_all_current(): 297 | """Test all current files can be loaded successfully""" 298 | src_configdir = os.path.join(SRCDIR, 'config') 299 | for cur, dirs, files in os.walk(src_configdir): 300 | for name in files: 301 | if not name.endswith('.ini'): 302 | continue 303 | if name == 'local.ini': 304 | continue 305 | path = os.path.join(cur, name) 306 | config = eib.ImageConfigParser() 307 | assert config.read_config_file(path, path.replace('/', '_')) 308 | assert get_combined_ini(config) != '' 309 | 310 | 311 | def test_environment_variables(config): 312 | """Test environment variable handling""" 313 | cases = [ 314 | ('build', 'opt', 'val', ('EIB_OPT', 'val')), 315 | ('sect', 'opt', 'val', ('EIB_SECT_OPT', 'val')), 316 | ('sect', 'opt', 'True', ('EIB_SECT_OPT', 'true')), 317 | ('sect', 'opt', 'False', ('EIB_SECT_OPT', 'false')), 318 | ('sect', 'opt', 'a\nb', ('EIB_SECT_OPT', 'a\nb')), 319 | ('sect', 'opt-a', 'val', ('EIB_SECT_OPT_A', 'val')), 320 | ('sect-1', 'opt-a', 'val', ('EIB_SECT_1_OPT_A', 'val')), 321 | ] 322 | 323 | for section, option, value, expected_env in cases: 324 | if section not in config: 325 | config.add_section(section) 326 | config[section][option] = value 327 | env = config.getenv(section, option) 328 | assert env == expected_env 329 | 330 | expected_environ = dict([case[3] for case in cases]) 331 | environ = config.get_environment() 332 | assert environ == expected_environ 333 | -------------------------------------------------------------------------------- /tests/eib/test_ostree_trusted_keys.py: -------------------------------------------------------------------------------- 1 | # Tests for eib ostree trusted key handling 2 | 3 | import eib 4 | import os 5 | import pytest 6 | import shutil 7 | 8 | from ..util import TESTSDIR 9 | 10 | 11 | @pytest.fixture 12 | def keys_config(tmp_path, config): 13 | config['build']['tmpdir'] = str(tmp_path) 14 | 15 | datadir = tmp_path / 'data' 16 | config['build']['datadir'] = str(datadir) 17 | 18 | localdatadir = tmp_path / 'local' / 'data' 19 | config['build']['localdatadir'] = str(localdatadir) 20 | 21 | return config 22 | 23 | 24 | def test_errors(keys_config): 25 | """Test errors from get_ostree_trusted_keys""" 26 | with pytest.raises(eib.ImageBuildError, match='No gpg keys directories'): 27 | eib.get_ostree_trusted_keys(keys_config) 28 | 29 | os.makedirs(os.path.join(keys_config['build']['datadir'], 'keys')) 30 | os.makedirs(os.path.join(keys_config['build']['localdatadir'], 'keys')) 31 | with pytest.raises(eib.ImageBuildError, match='No gpg keys in'): 32 | eib.get_ostree_trusted_keys(keys_config) 33 | 34 | 35 | def test_get_keys(keys_config): 36 | """Test the keys are gathered correctly""" 37 | keysdir = os.path.join(keys_config['build']['datadir'], 'keys') 38 | localkeysdir = os.path.join(keys_config['build']['localdatadir'], 39 | 'keys') 40 | testdatadir = os.path.join(TESTSDIR, 'data') 41 | os.makedirs(keysdir) 42 | os.makedirs(localkeysdir) 43 | 44 | shutil.copy2(os.path.join(testdatadir, 'test1.asc'), keysdir) 45 | keys = eib.get_ostree_trusted_keys(keys_config) 46 | assert keys == [os.path.join(keysdir, 'test1.asc')] 47 | 48 | shutil.copy2(os.path.join(testdatadir, 'test2.asc'), keysdir) 49 | keys = eib.get_ostree_trusted_keys(keys_config) 50 | assert keys == [ 51 | os.path.join(keysdir, 'test1.asc'), 52 | os.path.join(keysdir, 'test2.asc'), 53 | ] 54 | 55 | shutil.copy2(os.path.join(testdatadir, 'test3.asc'), localkeysdir) 56 | keys = eib.get_ostree_trusted_keys(keys_config) 57 | assert keys == [ 58 | os.path.join(keysdir, 'test1.asc'), 59 | os.path.join(keysdir, 'test2.asc'), 60 | os.path.join(localkeysdir, 'test3.asc'), 61 | ] 62 | -------------------------------------------------------------------------------- /tests/util.py: -------------------------------------------------------------------------------- 1 | # Test utilities and common settings 2 | 3 | from contextlib import contextmanager 4 | from functools import partial 5 | from http.server import SimpleHTTPRequestHandler 6 | import importlib.machinery 7 | import importlib.util 8 | import logging 9 | import os 10 | import shlex 11 | import subprocess 12 | import sys 13 | from threading import Thread 14 | 15 | try: 16 | from http.server import ThreadingHTTPServer 17 | except ImportError: 18 | # ThreadingHTTPServer was only added in Python 3.7. 19 | from http.server import HTTPServer 20 | from socketserver import ThreadingMixIn 21 | 22 | class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): 23 | daemon_threads = True 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | # Common directories 28 | TESTSDIR = os.path.dirname(__file__) 29 | SRCDIR = os.path.dirname(TESTSDIR) 30 | LIBDIR = os.path.join(SRCDIR, 'lib') 31 | 32 | # Test GPG keys in tests/data 33 | TEST_KEY_IDS = { 34 | 'test1': '5492C5F677139E42', 35 | 'test2': '292CDFA9EE1156B5', 36 | 'test3': '41FC13C44703AFF0', 37 | } 38 | 39 | 40 | def import_script(name, script): 41 | """Import a script as a module""" 42 | spec = importlib.util.spec_from_loader( 43 | name, 44 | importlib.machinery.SourceFileLoader(name, script) 45 | ) 46 | module = importlib.util.module_from_spec(spec) 47 | spec.loader.exec_module(module) 48 | return module 49 | 50 | 51 | # shlex.join added in python 3.8. 52 | if hasattr(shlex, 'join'): 53 | _join_cmd = shlex.join 54 | else: 55 | def _join_cmd(cmd): 56 | return ' '.join([shlex.quote(arg) for arg in cmd]) 57 | 58 | 59 | def run_command(cmd, check=True, **kwargs): 60 | """subprocess.run wrapper with logging""" 61 | logger.debug('$ %s', _join_cmd(cmd)) 62 | return subprocess.run(cmd, check=check, **kwargs) 63 | 64 | 65 | # Monkey patch SimpleHTTPRequestHandler to handle directory keyword arg 66 | # if python is less than 3.7. 67 | if sys.version_info[0:2] < (3, 7): 68 | import urllib.parse 69 | 70 | _SimpleHTTPRequestHandler = SimpleHTTPRequestHandler 71 | 72 | class SimpleHTTPRequestHandler(_SimpleHTTPRequestHandler): 73 | def __init__(self, *args, directory=None, **kwargs): 74 | if directory is None: 75 | directory = os.getcwd() 76 | self.directory = os.fspath(directory) 77 | super().__init__(*args, **kwargs) 78 | 79 | def translate_path(self, path): 80 | """Translate a /-separated PATH to the local filename syntax. 81 | 82 | Components that mean special things to the local file system 83 | (e.g. drive or directory names) are ignored. (XXX They should 84 | probably be diagnosed.) 85 | 86 | """ 87 | # abandon query parameters 88 | path = path.split('?', 1)[0] 89 | path = path.split('#', 1)[0] 90 | # Don't forget explicit trailing slash when normalizing. Issue17324 91 | trailing_slash = path.rstrip().endswith('/') 92 | try: 93 | path = urllib.parse.unquote(path, errors='surrogatepass') 94 | except UnicodeDecodeError: 95 | path = urllib.parse.unquote(path) 96 | path = os.path.normpath(path) 97 | words = path.split('/') 98 | words = filter(None, words) 99 | path = self.directory 100 | for word in words: 101 | if os.path.dirname(word) or word in (os.curdir, os.pardir): 102 | # Ignore components that are not a simple file/directory name 103 | continue 104 | path = os.path.join(path, word) 105 | if trailing_slash: 106 | path += '/' 107 | logger.info('Translated path: %s', path) 108 | return path 109 | 110 | 111 | class LoggerHTTPRequestHandler(SimpleHTTPRequestHandler): 112 | """HTTP request handler logging to a Logger""" 113 | def log_message(self, format, *args): 114 | message = format % args 115 | logger.debug( 116 | f'{self.client_address[0]}:{self.client_address[1]} {message}' 117 | ) 118 | 119 | 120 | @contextmanager 121 | def http_server_thread(directory): 122 | """HTTP server running in separate thread""" 123 | handler = partial(LoggerHTTPRequestHandler, directory=directory) 124 | with ThreadingHTTPServer(('127.0.0.1', 0), handler) as server: 125 | host, port = server.socket.getsockname() 126 | url = f'http://{host}:{port}' 127 | logger.info(f'Server bound to {url}') 128 | 129 | # Start the server loop in a separate thread. 130 | logger.debug('Starting HTTP server') 131 | thread = Thread(target=server.serve_forever) 132 | thread.start() 133 | try: 134 | # Yield to the caller with the bound socket name. 135 | yield url 136 | finally: 137 | # Shutdown the server and wait for the thread to end after 138 | # the server handles the shutdown request. 139 | logger.debug('Shutting down HTTP server') 140 | server.shutdown() 141 | thread.join(5) 142 | --------------------------------------------------------------------------------