├── .fmf └── version ├── .gitignore ├── tmt ├── tests │ ├── greenboot-test.fmf │ └── test.sh └── plans │ └── greenboot-test.fmf ├── tests ├── launch_all_tests.sh ├── testing_files │ ├── 10_failing_check.sh │ ├── 30_red_script.sh │ ├── 20_green_script.sh │ └── fedora_iot.conf ├── ansible.cfg ├── check_watchdog_support.bats ├── files │ ├── rhel-9-6.repo │ ├── rhel-10-0.repo │ ├── fedora-39.json │ ├── centos-stream-9.json │ └── rhel-9-6-0.json ├── rpm_ostree_check_fallback.bats ├── key │ ├── ostree_key.pub │ └── ostree_key ├── greenboot_red.bats ├── greenboot_green.bats ├── common.bash ├── redboot_auto_reboot.bats ├── greenboot_check.bats ├── greenboot_check_fail_wanted.bats ├── greenboot_check_fail_required.bats ├── grub2_set_counter.bats ├── greenboot-bootc.yaml ├── greenboot-rpm-ostree.yaml ├── greenboot-bootc-qcow2.sh ├── check-ostree.yaml ├── greenboot-bootc-anaconda-iso.sh └── greenboot-rpm-ostree.sh ├── usr ├── lib │ ├── motd.d │ │ └── boot-status │ ├── tmpfiles.d │ │ └── greenboot-status-motd.conf │ ├── systemd │ │ └── system │ │ │ ├── greenboot-healthcheck.service.d │ │ │ └── 10-network-online.conf │ │ │ ├── redboot.target │ │ │ ├── redboot-task-runner.service │ │ │ ├── redboot-auto-reboot.service │ │ │ ├── greenboot-task-runner.service │ │ │ ├── greenboot-grub2-set-success.service │ │ │ ├── greenboot-status.service │ │ │ ├── greenboot-healthcheck.service │ │ │ ├── greenboot-loading-message.service │ │ │ ├── greenboot-rpm-ostree-grub2-check-fallback.service │ │ │ └── greenboot-grub2-set-counter.service │ └── greenboot │ │ └── check │ │ ├── wanted.d │ │ ├── 00_wanted_scripts_start.sh │ │ └── 01_update_platforms_check.sh │ │ └── required.d │ │ ├── 00_required_scripts_start.sh │ │ ├── 01_repository_dns_check.sh │ │ └── 02_watchdog.sh └── libexec │ └── greenboot │ ├── greenboot-loading-message │ ├── greenboot-boot-remount │ ├── greenboot-grub2-set-success │ ├── redboot-auto-reboot │ ├── greenboot-grub2-set-counter │ ├── greenboot-status │ ├── greenboot-rpm-ostree-grub2-check-fallback │ └── greenboot ├── development ├── legacy │ ├── 10_failing_check.sh │ ├── failing.service │ ├── greenboot-failing-unit.spec │ └── README.md └── README.md ├── Dockerfile ├── grub2 └── 08_greenboot.cfg ├── etc └── greenboot │ └── greenboot.conf ├── .github └── workflows │ ├── main.yml │ ├── comment-ci.yaml │ ├── greenboot-ci.yaml │ └── greenboot-rs.yaml ├── RELEASING.md ├── .packit.yaml ├── README.md └── greenboot.spec /.fmf/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.vagrant 2 | -------------------------------------------------------------------------------- /tmt/tests/greenboot-test.fmf: -------------------------------------------------------------------------------- 1 | test: ./test.sh 2 | duration: 90m 3 | -------------------------------------------------------------------------------- /tests/launch_all_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bats *.bats --print-output-on-failure --jobs 1 4 | -------------------------------------------------------------------------------- /usr/lib/motd.d/boot-status: -------------------------------------------------------------------------------- 1 | greenboot's health checks haven't run, yet. Boot Status is INDETERMINATE. -------------------------------------------------------------------------------- /development/legacy/10_failing_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "This is a failing script" 5 | 6 | exit 1 7 | -------------------------------------------------------------------------------- /tests/testing_files/10_failing_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "This is a failing script" 5 | 6 | exit 1 7 | -------------------------------------------------------------------------------- /tests/testing_files/30_red_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "This is run when the boot is failed" 5 | 6 | exit 0 7 | -------------------------------------------------------------------------------- /usr/lib/tmpfiles.d/greenboot-status-motd.conf: -------------------------------------------------------------------------------- 1 | #Type Path Mode User Group Age Argument 2 | f /run/motd.d/boot-status - - - - - 3 | -------------------------------------------------------------------------------- /tests/testing_files/20_green_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "This is run when the boot is successful" 5 | 6 | exit 0 7 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-healthcheck.service.d/10-network-online.conf: -------------------------------------------------------------------------------- 1 | [Unit] 2 | After=network-online.target 3 | Wants=network-online.target 4 | -------------------------------------------------------------------------------- /usr/lib/greenboot/check/wanted.d/00_wanted_scripts_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "Running greenboot Wanted Health Check Scripts" 5 | -------------------------------------------------------------------------------- /usr/lib/greenboot/check/required.d/00_required_scripts_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "Running greenboot Required Health Check Scripts" 5 | -------------------------------------------------------------------------------- /tests/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | timeout = 30 3 | # human-readable stdout/stderr results display 4 | stdout_callback = yaml 5 | 6 | [ssh_connection] 7 | scp_if_ssh=True 8 | pipelining=False 9 | -------------------------------------------------------------------------------- /tests/testing_files/fedora_iot.conf: -------------------------------------------------------------------------------- 1 | [remote "fedora-iot"] 2 | url=https://ostree.fedoraproject.org/iot 3 | gpg-verify=true 4 | gpgkeypath=/etc/pki/rpm-gpg/ 5 | contenturl=mirrorlist=https://ostree.fedoraproject.org/iot/mirrorlist 6 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot-loading-message: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "GREENBOOT is currently performing the provided checks." | tee /run/motd.d/boot-status 3 | echo -n "Log in again in a minute or check /run/motd.d/boot-status to know the result of the checks" | tee --append /run/motd.d/boot-status 4 | -------------------------------------------------------------------------------- /tests/check_watchdog_support.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | source $GREENBOOT_DEFAULT_CHECK_PATH/required.d/02_watchdog.sh --source-only 5 | } 6 | 7 | @test "Ensure watchdog check is working" { 8 | run check_if_current_boot_is_wd_triggered 9 | [ "$status" -eq 0 ] 10 | } 11 | -------------------------------------------------------------------------------- /tests/files/rhel-9-6.repo: -------------------------------------------------------------------------------- 1 | [RHEL-96-NIGHTLY-BaseOS] 2 | name=baseos 3 | baseurl=http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/BaseOS/x86_64/os 4 | enabled=1 5 | gpgcheck=0 6 | [RHEL-96-NIGHTLY-AppStream] 7 | name=appstream 8 | baseurl=http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/AppStream/x86_64/os/ 9 | enabled=1 10 | gpgcheck=0 -------------------------------------------------------------------------------- /tests/files/rhel-10-0.repo: -------------------------------------------------------------------------------- 1 | [RHEL-10-NIGHTLY-BaseOS] 2 | name=baseos 3 | baseurl=http://REPLACE_ME_HERE/rhel-10/nightly/RHEL-10/latest-RHEL-10.0/compose/BaseOS/x86_64/os 4 | enabled=1 5 | gpgcheck=0 6 | [RHEL-10-NIGHTLY-AppStream] 7 | name=appstream 8 | baseurl=http://REPLACE_ME_HERE/rhel-10/nightly/RHEL-10/latest-RHEL-10.0/compose/AppStream/x86_64/os/ 9 | enabled=1 10 | gpgcheck=0 11 | -------------------------------------------------------------------------------- /tests/rpm_ostree_check_fallback.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | $GRUB2_SET_COUNTER_BIN_PATH 5 | } 6 | 7 | @test "Test there's no rollback when boot_counter != -1" { 8 | run $RPM_OSTREE_CHECK_FALLBACK_PATH 9 | [[ "$output" != *"FALLBACK BOOT DETECTED"* ]] 10 | } 11 | 12 | function teardown() { 13 | $GRUB2_EDITENV - unset boot_counter 14 | $GRUB2_EDITENV - unset boot_success 15 | } 16 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/redboot.target: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=Generic red boot target 12 | Conflicts=boot-complete.target 13 | -------------------------------------------------------------------------------- /tmt/tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euox pipefail 3 | 4 | cd ../../tests || exit 1 5 | 6 | function run_tests() { 7 | if [ "$TEST_CASE" = "bootc-qcow2" ]; then 8 | ./greenboot-bootc-qcow2.sh 9 | elif [ "$TEST_CASE" = "bootc-anaconda-iso" ]; then 10 | ./greenboot-bootc-anaconda-iso.sh 11 | elif [ "$TEST_CASE" = "rpm-ostree-commit" ]; then 12 | ./greenboot-rpm-ostree.sh 13 | else 14 | echo "Error: Test case $TEST_CASE not found!" 15 | exit 1 16 | fi 17 | } 18 | 19 | run_tests 20 | exit 0 21 | -------------------------------------------------------------------------------- /development/legacy/failing.service: -------------------------------------------------------------------------------- 1 | # This file is part of greenboot. 2 | # 3 | # greenboot is free software; you can redistribute it and/or modify it 4 | # under the terms of the GNU Lesser General Public License as published by 5 | # the Free Software Foundation; either version 2.1 of the License, or 6 | # (at your option) any later version. 7 | 8 | [Unit] 9 | Description=greenboot Failing Check Example 10 | Before=boot-complete.target 11 | 12 | [Service] 13 | Type=oneshot 14 | ExecStart=/bin/false 15 | 16 | [Install] 17 | RequiredBy=boot-complete.target 18 | -------------------------------------------------------------------------------- /tests/key/ostree_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzxo5dEcS+LDK/OFAfHo6740EyoDM8aYaCkBala0FnWfMMTOq7PQe04ahB0eFLS3IlQtK5bpgzxBdFGVqF6uT5z4hhaPjQec0G3+BD5Pxo6V+SxShKZo+ZNGU3HVrF9p2V7QH0YFQj5B8F6AicA3fYh2BVUFECTPuMpy5A52ufWu0r4xOFmbU7SIhRQRAQz2u4yjXqBsrpYptAvyzzoN4gjUhNnwOHSPsvFpWoBFkWmqn0ytgHg3Vv9DlHW+45P02QH1UFedXR2MqLnwRI30qqtaOkVS+9rE/dhnR+XPpHHG+hv2TgMDAuQ3IK7Ab5m/yCbN73cxFifH4LST0vVG3Jx45xn+GTeHHhfkAfBSCtya6191jixbqyovpRunCBKexI5cfRPtWOitM3m7Mq26r7LpobMM+oOLUm4p0KKNIthWcmK9tYwXWSuGGfUQ+Y8gt7E0G06ZGbCPHOrxJ8lYQqXsif04piONPA/c9Hq43O99KPNGShONCS9oPFdOLRT3U= ostree-image-test 2 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/redboot-task-runner.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=greenboot Failure Scripts Runner 12 | Before=redboot.target 13 | 14 | [Service] 15 | Type=oneshot 16 | RemainAfterExit=yes 17 | ExecStart=/usr/libexec/greenboot/greenboot red 18 | 19 | [Install] 20 | RequiredBy=redboot.target 21 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/redboot-auto-reboot.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=Reboot on red boot status 12 | DefaultDependencies=no 13 | After=redboot-task-runner.service 14 | 15 | [Service] 16 | Type=oneshot 17 | ExecStart=/usr/libexec/greenboot/redboot-auto-reboot 18 | 19 | [Install] 20 | WantedBy=redboot.target 21 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-task-runner.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=greenboot Success Scripts Runner 12 | Requires=boot-complete.target 13 | After=boot-complete.target 14 | 15 | [Service] 16 | Type=oneshot 17 | RemainAfterExit=yes 18 | ExecStart=/usr/libexec/greenboot/greenboot green 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora 2 | RUN dnf install -y git findutils systemd grub2-tools-minimal util-linux jq 3 | 4 | RUN git clone https://github.com/bats-core/bats-core.git 5 | WORKDIR /bats-core 6 | RUN ./install.sh /usr/local 7 | 8 | COPY ./usr/libexec/greenboot /usr/libexec/greenboot 9 | COPY ./usr/lib/greenboot/check /usr/lib/greenboot/check 10 | RUN mkdir -p /etc/greenboot/{green.d,red.d,check} 11 | RUN mkdir /etc/greenboot/check/{required.d,wanted.d} 12 | COPY ./etc/greenboot/greenboot.conf /etc/greenboot/greenboot.conf 13 | 14 | COPY ./tests /testing 15 | COPY ./tests/testing_files/fedora_iot.conf /etc/ostree/remotes.d/fedora_iot.conf 16 | 17 | WORKDIR /testing 18 | ENTRYPOINT [ "/bin/bash", "launch_all_tests.sh" ] 19 | -------------------------------------------------------------------------------- /tmt/plans/greenboot-test.fmf: -------------------------------------------------------------------------------- 1 | summary: Greenboot test plan 2 | discover: 3 | how: fmf 4 | test: greenboot-test 5 | execute: 6 | how: tmt 7 | provision: 8 | hardware: 9 | virtualization: 10 | is-supported: true 11 | cpu: 12 | processors: ">= 2" 13 | memory: ">= 6 GB" 14 | 15 | /bootc-qcow2: 16 | summary: Test greenboot with bootc qcow2 image 17 | environment+: 18 | TEST_CASE: bootc-qcow2 19 | 20 | /bootc-anaconda-iso: 21 | summary: Test greenboot with bootc anaconda iso 22 | environment+: 23 | TEST_CASE: bootc-anaconda-iso 24 | 25 | /rpm-ostree-commit: 26 | summary: Test greenboot with rpm-ostree commit 27 | environment+: 28 | TEST_CASE: rpm-ostree-commit 29 | 30 | -------------------------------------------------------------------------------- /tests/greenboot_red.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | mkdir -p $GREENBOOT_DEFAULT_RED_PATH $GREENBOOT_ETC_RED_PATH 5 | cp testing_files/30_red_script.sh $GREENBOOT_DEFAULT_RED_PATH/30_red_script.sh 6 | cp testing_files/30_red_script.sh $GREENBOOT_ETC_RED_PATH/40_red_script.sh 7 | } 8 | 9 | @test "Test greenboot red command" { 10 | run $GREENBOOT_BIN_PATH red 11 | [ "$status" -eq 0 ] 12 | [[ "$output" == *"Health Check FAILURE"* ]] 13 | [[ "$output" == *"30_red_script.sh"* ]] 14 | [[ "$output" == *"40_red_script.sh"* ]] 15 | } 16 | 17 | function teardown() { 18 | rm $GREENBOOT_DEFAULT_RED_PATH/30_red_script.sh 19 | rm $GREENBOOT_ETC_RED_PATH/40_red_script.sh 20 | } 21 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-grub2-set-success.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=Mark boot as successful in grubenv 12 | Requires=boot-complete.target 13 | After=boot-complete.target 14 | 15 | [Service] 16 | Type=oneshot 17 | RemainAfterExit=yes 18 | ExecStart=/usr/libexec/greenboot/greenboot-grub2-set-success 19 | PrivateMounts=yes 20 | 21 | [Install] 22 | WantedBy=multi-user.target 23 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-status.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=greenboot MotD Generator 12 | After=greenboot-healthcheck.service 13 | After=greenboot-task-runner.service 14 | After=redboot-task-runner.service 15 | 16 | [Service] 17 | Type=oneshot 18 | RemainAfterExit=yes 19 | ExecStart=/usr/libexec/greenboot/greenboot-status 20 | 21 | [Install] 22 | WantedBy=multi-user.target 23 | -------------------------------------------------------------------------------- /grub2/08_greenboot.cfg: -------------------------------------------------------------------------------- 1 | # greenboot support, aka boot counter and boot success reporting 2 | # This will be installed via bootupd 3 | insmod increment 4 | # Check if boot_counter exists and boot_success=0 to activate this behaviour. 5 | if [ -n "${boot_counter}" -a "${boot_success}" = "0" ]; then 6 | # if countdown has ended, choose to boot rollback deployment, 7 | # i.e. default=1 on OSTree-based systems. 8 | if [ "${boot_counter}" = "0" -o "${boot_counter}" = "-1" ]; then 9 | set default=1 10 | set boot_counter=-1 11 | # otherwise decrement boot_counter 12 | else 13 | decrement boot_counter 14 | fi 15 | save_env boot_counter 16 | fi 17 | 18 | # Reset boot_success for current boot 19 | set boot_success=0 20 | save_env boot_success 21 | -------------------------------------------------------------------------------- /tests/greenboot_green.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | mkdir -p $GREENBOOT_DEFAULT_GREEN_PATH $GREENBOOT_ETC_GREEN_PATH 5 | cp testing_files/20_green_script.sh $GREENBOOT_DEFAULT_GREEN_PATH/20_green_script.sh 6 | cp testing_files/20_green_script.sh $GREENBOOT_ETC_GREEN_PATH/30_green_script.sh 7 | } 8 | 9 | @test "Test greenboot green command" { 10 | run $GREENBOOT_BIN_PATH green 11 | [ "$status" -eq 0 ] 12 | [[ "$output" == *"Health Check SUCCESS"* ]] 13 | [[ "$output" == *"20_green_script.sh"* ]] 14 | [[ "$output" == *"30_green_script.sh"* ]] 15 | } 16 | 17 | function teardown() { 18 | rm $GREENBOOT_DEFAULT_GREEN_PATH/20_green_script.sh 19 | rm $GREENBOOT_ETC_GREEN_PATH/30_green_script.sh 20 | } 21 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot-boot-remount: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | # Boolean variable to track if /boot was initially mounted as read-only 5 | # Ensure compatibility with rpm-ostree where /boot is rw but in bootc /boot is ro 6 | boot_was_ro=false 7 | 8 | # Remount /boot as read-only if it was mounted as read-only ealier 9 | function remount_boot_ro { 10 | if $boot_was_ro; then 11 | mount -o remount,bind,ro /boot || exit 13 12 | fi 13 | return 14 | } 15 | 16 | # Remount /boot as read-write if it was mounted as read-only 17 | function remount_boot_rw { 18 | if grep -q " /boot .* ro," /proc/mounts; then 19 | mount -o remount,rw /boot || exit 13 20 | boot_was_ro=true 21 | fi 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-healthcheck.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=greenboot Health Checks Runner 12 | Before=boot-complete.target 13 | OnFailure=redboot.target 14 | OnFailureJobMode=fail 15 | 16 | [Service] 17 | Type=oneshot 18 | RemainAfterExit=yes 19 | ExecStart=/usr/libexec/greenboot/greenboot check 20 | 21 | [Install] 22 | RequiredBy=boot-complete.target 23 | WantedBy=multi-user.target 24 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-loading-message.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=greenboot message on MOTD while checks are being performed 12 | After=greenboot-rpm-ostree-grub2-check-fallback.service 13 | Before=greenboot-healthcheck.service 14 | 15 | [Service] 16 | Type=oneshot 17 | RemainAfterExit=yes 18 | ExecStart=/usr/libexec/greenboot/greenboot-loading-message 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot-grub2-set-success: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | source /usr/libexec/greenboot/greenboot-boot-remount 6 | remount_boot_rw 7 | 8 | # Run the grub2-editenv commands 9 | if ! /usr/bin/grub2-editenv /boot/grub2/grubenv set boot_success=1; then 10 | # If the first command fails, remount /boot as read-only and exit with failure 11 | remount_boot_ro 12 | exit 1 13 | fi 14 | 15 | if ! /usr/bin/grub2-editenv /boot/grub2/grubenv unset boot_counter; then 16 | # If the second command fails, remount /boot as read-only and exit with failure 17 | remount_boot_ro 18 | exit 1 19 | fi 20 | 21 | # Remount /boot as read-only if it was mounted as read-write 22 | remount_boot_ro 23 | 24 | # If everything succeeded, exit with success 25 | exit 0 26 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-rpm-ostree-grub2-check-fallback.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=Check for fallback boot 12 | Before=greenboot-healthcheck.service 13 | Before=greenboot-grub2-set-success.service 14 | 15 | [Service] 16 | Type=oneshot 17 | RemainAfterExit=yes 18 | ExecStart=/usr/libexec/greenboot/greenboot-rpm-ostree-grub2-check-fallback 19 | PrivateMounts=yes 20 | 21 | [Install] 22 | RequiredBy=greenboot-healthcheck.service 23 | -------------------------------------------------------------------------------- /tests/common.bash: -------------------------------------------------------------------------------- 1 | GREENBOOT_USR_ROOT_PATH="/usr/libexec/greenboot" 2 | GREENBOOT_ETC_ROOT_PATH="/etc/greenboot" 3 | GREENBOOT_USR_LIB_ROOT_PATH="/usr/lib/greenboot" 4 | 5 | GREENBOOT_BIN_PATH="$GREENBOOT_USR_ROOT_PATH/greenboot" 6 | GRUB2_SET_COUNTER_BIN_PATH="$GREENBOOT_USR_ROOT_PATH/greenboot-grub2-set-counter" 7 | RPM_OSTREE_CHECK_FALLBACK_PATH="$GREENBOOT_USR_ROOT_PATH/greenboot-rpm-ostree-grub2-check-fallback" 8 | GRUB2_EDITENV=/usr/bin/grub2-editenv 9 | 10 | GREENBOOT_DEFAULT_CHECK_PATH="$GREENBOOT_USR_LIB_ROOT_PATH/check" 11 | GREENBOOT_ETC_CHECK_PATH="$GREENBOOT_ETC_ROOT_PATH/check" 12 | GREENBOOT_DEFAULT_GREEN_PATH="$GREENBOOT_USR_LIB_ROOT_PATH/green.d" 13 | GREENBOOT_DEFAULT_RED_PATH="$GREENBOOT_USR_LIB_ROOT_PATH/red.d" 14 | GREENBOOT_ETC_GREEN_PATH="$GREENBOOT_ETC_ROOT_PATH/green.d" 15 | GREENBOOT_ETC_RED_PATH="$GREENBOOT_ETC_ROOT_PATH/red.d" 16 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/greenboot-grub2-set-counter.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of greenboot. 4 | # 5 | # greenboot is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=Set grub2 boot counter in preparation of upgrade 12 | DefaultDependencies=no 13 | Before=ostree-finalize-staged.service 14 | Conflicts=greenboot-grub2-set-success.service 15 | RequiresMountsFor=/boot 16 | 17 | [Service] 18 | Type=oneshot 19 | RemainAfterExit=yes 20 | ExecStart=/usr/libexec/greenboot/greenboot-grub2-set-counter 21 | PrivateMounts=yes 22 | 23 | [Install] 24 | RequiredBy=ostree-finalize-staged.service 25 | -------------------------------------------------------------------------------- /tests/redboot_auto_reboot.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | REDBOOT_AUTO_REBOOT=$GREENBOOT_USR_ROOT_PATH/redboot-auto-reboot 3 | 4 | function setup() { 5 | $GRUB2_EDITENV - unset boot_counter 6 | } 7 | 8 | @test "Test system unhealthy but boot_counter unset" { 9 | run $REDBOOT_AUTO_REBOOT 10 | [[ "$output" == *"<0>SYSTEM is UNHEALTHY"* ]] 11 | [ "$status" = 1 ] 12 | } 13 | 14 | @test "Test fallback boot detected but system still unhealthy" { 15 | run $GRUB2_EDITENV - set boot_counter=-1 16 | run $REDBOOT_AUTO_REBOOT 17 | [[ "$output" == *"<0>FALLBACK BOOT DETECTED"* ]] 18 | [ "$status" = 2 ] 19 | } 20 | 21 | @test "Test system unhealthy but bootloader entry count -le 1 " { 22 | run $GRUB2_EDITENV - set boot_counter=1 23 | run $REDBOOT_AUTO_REBOOT 24 | [[ "$output" == *"<0>SYSTEM is UNHEALTHY"* ]] 25 | [ "$status" = 3 ] 26 | } 27 | 28 | function teardown() { 29 | $GRUB2_EDITENV - unset boot_counter 30 | } 31 | -------------------------------------------------------------------------------- /development/legacy/greenboot-failing-unit.spec: -------------------------------------------------------------------------------- 1 | Name: greenboot-failing-unit 2 | Version: 1.0 3 | Release: 1%{?dist} 4 | Summary: Failing healthcheck unit for testing greenboot red status 5 | License: LGPLv2+ 6 | URL: https://github.com/%{repo_owner}/%{repo_name} 7 | BuildArch: noarch 8 | Requires: greenboot 9 | 10 | %description 11 | %{summary}. 12 | 13 | %prep 14 | 15 | %build 16 | 17 | %install 18 | mkdir -p %{buildroot}%{_sysconfdir}/greenboot/check/required.d/ 19 | cat < %{buildroot}%{_sysconfdir}/greenboot/check/required.d/10_failing_check.sh 20 | #!/bin/bash 21 | set -euo pipefail 22 | echo "This is a failing script" 23 | exit 1 24 | EOF 25 | 26 | %post 27 | 28 | %preun 29 | 30 | %postun 31 | 32 | %files 33 | %{_sysconfdir}/greenboot/check/required.d/10_failing_check.sh 34 | 35 | %changelog 36 | * Wed Feb 13 2019 Christian Glombek - 1.0-1 37 | - Initial RPM Spec 38 | -------------------------------------------------------------------------------- /etc/greenboot/greenboot.conf: -------------------------------------------------------------------------------- 1 | # Greenboot configuration file 2 | 3 | ## Generic 4 | # GREENBOOT_MAX_BOOT_ATTEMPTS=3 5 | 6 | ## Watchdog 7 | ### This variable controls 8 | ### This value can be "true, TRUE, True..." as it will be lowercased. 9 | ### Set it to anything else to disable this check. 10 | GREENBOOT_WATCHDOG_CHECK_ENABLED=true 11 | 12 | ### This variable is the number of hours after an upgrade that we consider 13 | ### the new deployment as culprit of reboot. 14 | ### It has to be a positive integer. Defaults to 24 (hours). 15 | # GREENBOOT_WATCHDOG_GRACE_PERIOD=24 16 | 17 | ### This variable allows you to specify healthchecks to be skipped. 18 | ### The healthcheck must be specified by script name, as in the 19 | ### example below. Multiple healthchecks may be skipped by separating 20 | ### the script names with spaces. 21 | ### NOTE: Script names must be spelled EXACTLY. Typos will result in 22 | ### unwanted behaviour. 23 | ### DISABLED_HEALTHCHECKS=( 24 | ### "01_repository_dns_check.sh" 25 | ### "02_watchdog.sh" 26 | ### ) 27 | DISABLED_HEALTHCHECKS=() 28 | -------------------------------------------------------------------------------- /tests/greenboot_check.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | # 02_watchdog.sh can't be checked within the container at the moment 5 | # due to rpm-ostree, hence moving it out of the required directory 6 | # for this test 7 | mv $GREENBOOT_DEFAULT_CHECK_PATH/required.d/02_watchdog.sh /tmp/02_watchdog.sh 8 | 9 | # This checks that the /etc/greenboot/check path works as well 10 | # as the /usr/lib/greenboot/check one 11 | mv $GREENBOOT_DEFAULT_CHECK_PATH/wanted.d/* $GREENBOOT_ETC_CHECK_PATH/wanted.d/ 12 | } 13 | 14 | @test "Test greenboot with unknown argument" { 15 | run $GREENBOOT_BIN_PATH bananas 16 | [ "$status" -eq 1 ] 17 | [ "$output" = "Unknown argument, exiting." ] 18 | } 19 | 20 | @test "Test greenboot check with the default hc scripts" { 21 | run $GREENBOOT_BIN_PATH check 22 | [ "$status" -eq 0 ] 23 | [[ "$output" == *"Running Required Health Check Scripts..."* ]] 24 | } 25 | 26 | function teardown() { 27 | mv /tmp/02_watchdog.sh $GREENBOOT_DEFAULT_CHECK_PATH/required.d/02_watchdog.sh 28 | mv $GREENBOOT_ETC_CHECK_PATH/wanted.d/* $GREENBOOT_DEFAULT_CHECK_PATH/wanted.d/ 29 | } 30 | -------------------------------------------------------------------------------- /tests/greenboot_check_fail_wanted.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | mkdir -p $GREENBOOT_DEFAULT_CHECK_PATH $GREENBOOT_ETC_CHECK_PATH 5 | 6 | # 02_watchdog.sh can't be checked within the container at the moment 7 | # due to rpm-ostree, hence moving it out of the required directory 8 | # for this test 9 | mv $GREENBOOT_DEFAULT_CHECK_PATH/required.d/02_watchdog.sh /tmp/02_watchdog.sh 10 | 11 | cp testing_files/10_failing_check.sh $GREENBOOT_DEFAULT_CHECK_PATH/wanted.d/10_failing_check.sh 12 | cp testing_files/10_failing_check.sh $GREENBOOT_ETC_CHECK_PATH/wanted.d/20_failing_check.sh 13 | } 14 | 15 | @test "Test greenboot check with wanted scripts failing" { 16 | run $GREENBOOT_BIN_PATH check 17 | [ "$status" -eq 0 ] 18 | [[ "$output" == *"10_failing_check.sh"* ]] 19 | [[ "$output" == *"20_failing_check.sh"* ]] 20 | } 21 | 22 | function teardown() { 23 | rm $GREENBOOT_DEFAULT_CHECK_PATH/wanted.d/10_failing_check.sh 24 | rm $GREENBOOT_ETC_CHECK_PATH/wanted.d/20_failing_check.sh 25 | mv /tmp/02_watchdog.sh $GREENBOOT_DEFAULT_CHECK_PATH/required.d/02_watchdog.sh 26 | } 27 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/redboot-auto-reboot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | bootloader_entries=(/boot/loader/entries/*) 5 | bootloader_entry_count=${#bootloader_entries[@]} 6 | 7 | # If boot_counter is unset, do nothing 8 | if ! grub2-editenv list | grep -q "^boot_counter="; then 9 | echo "<0>SYSTEM is UNHEALTHY, but boot_counter is unset in grubenv. Manual intervention necessary." 10 | exit 1 11 | # In case we've booted into our fallback deployment and still don't reach boot-complete.target, do nothing. 12 | # This condition will never be reached if greenboot-rpm-ostree-grub2-check-fallback.service 13 | # is installed and enabled. 14 | elif grub2-editenv list | grep -q "^boot_counter=-1$"; then 15 | echo "<0>FALLBACK BOOT DETECTED but SYSTEM is still UNHEALTHY. Manual intervention necessary." 16 | exit 2 17 | # If we have only one (or less) bootloader entries, don't reboot 18 | elif [ "$bootloader_entry_count" -le 1 ]; then 19 | echo "<0>SYSTEM is UNHEALTHY, but bootlader entry count is $bootloader_entry_count. Manual intervention necessary." 20 | exit 3 21 | fi 22 | 23 | echo "<1>SYSTEM is UNHEALTHY. Rebooting..." 24 | reboot 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Run CI tests 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | bats_test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Build the Docker image 11 | run: docker build . --file Dockerfile --tag test-image 12 | - name: Run tests 13 | run: docker run --rm --name test-greenboot -v /run/systemd/journal:/run/systemd/journal test-image 14 | shellcheck: 15 | name: Shellcheck 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | contents: read 20 | security-events: write 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | 27 | - id: ShellCheck 28 | name: Differential ShellCheck 29 | uses: redhat-plumbers-in-action/differential-shellcheck@v4 30 | with: 31 | exclude-path: tests/** 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | - if: ${{ always() }} 35 | name: Upload artifact with ShellCheck defects in SARIF format 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: Differential ShellCheck SARIF 39 | path: ${{ steps.ShellCheck.outputs.sarif }} 40 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot-grub2-set-counter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | source /usr/libexec/greenboot/greenboot-boot-remount 5 | 6 | GREENBOOT_CONFIGURATION_FILE=/etc/greenboot/greenboot.conf 7 | if test -f "$GREENBOOT_CONFIGURATION_FILE"; then 8 | # shellcheck source=etc/greenboot/greenboot.conf 9 | source $GREENBOOT_CONFIGURATION_FILE 10 | fi 11 | 12 | if [ -n "$1" ]; then 13 | max_boot_attempts=$1 14 | elif [ -n "$GREENBOOT_MAX_BOOT_ATTEMPTS" ]; then 15 | max_boot_attempts=$GREENBOOT_MAX_BOOT_ATTEMPTS 16 | else 17 | max_boot_attempts=3 # default to 3 attempts 18 | fi 19 | 20 | 21 | remount_boot_rw 22 | 23 | if ! /usr/bin/grub2-editenv - set boot_counter="$max_boot_attempts"; then 24 | # If the first command fails, remount /boot as read-only and exit with failure 25 | remount_boot_ro 26 | exit 1 27 | fi 28 | 29 | if ! /usr/bin/grub2-editenv /boot/grub2/grubenv set boot_success=0; then 30 | # If the first command fails, remount /boot as read-only and exit with failure 31 | remount_boot_ro 32 | exit 1 33 | fi 34 | 35 | # Revert /boot as read-only 36 | remount_boot_ro 37 | 38 | echo "<3>GRUB2 environment variables have been set for system upgrade. Max boot attempts is $max_boot_attempts" 39 | -------------------------------------------------------------------------------- /usr/lib/greenboot/check/wanted.d/01_update_platforms_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | REPOS_DIRECTORY=/etc/ostree/remotes.d 5 | URLS_WITH_PROBLEMS=() 6 | 7 | get_update_platform_urls() { 8 | mapfile -t UPDATE_PLATFORM_URLS < <(grep -P -ho 'http[s]?.*' "${REPOS_DIRECTORY}"/*) 9 | if [[ ${#UPDATE_PLATFORM_URLS[@]} -eq 0 ]]; then 10 | echo "No update platforms found, this can be a mistake" 11 | exit 1 12 | fi 13 | } 14 | 15 | assert_update_platforms_are_responding() { 16 | for UPDATE_PLATFORM_URL in "${UPDATE_PLATFORM_URLS[@]}"; do 17 | HTTP_STATUS=$(curl -o /dev/null -Isw '%{http_code}\n' "$UPDATE_PLATFORM_URL" || echo "Unreachable") 18 | if ! [[ $HTTP_STATUS == 2* ]] && ! [[ $HTTP_STATUS == 3* ]]; then 19 | URLS_WITH_PROBLEMS+=( "$UPDATE_PLATFORM_URL" ) 20 | fi 21 | done 22 | if [[ ${#URLS_WITH_PROBLEMS[@]} -eq 0 ]]; then 23 | echo "We can connect to all update platforms" 24 | exit 0 25 | else 26 | echo "There are problems connecting with the following URLs:" 27 | echo "${URLS_WITH_PROBLEMS[*]}" 28 | exit 1 29 | fi 30 | } 31 | 32 | if [[ ! -d $REPOS_DIRECTORY ]]; then 33 | echo "${REPOS_DIRECTORY} doesn't exist" 34 | exit 1 35 | fi 36 | 37 | get_update_platform_urls 38 | assert_update_platforms_are_responding 39 | -------------------------------------------------------------------------------- /tests/greenboot_check_fail_required.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | function setup() { 4 | mkdir -p $GREENBOOT_DEFAULT_CHECK_PATH $GREENBOOT_ETC_CHECK_PATH 5 | cp testing_files/10_failing_check.sh $GREENBOOT_DEFAULT_CHECK_PATH/required.d/10_failing_check.sh 6 | cp testing_files/10_failing_check.sh $GREENBOOT_DEFAULT_CHECK_PATH/required.d/20_failing_check.sh 7 | cp testing_files/10_failing_check.sh $GREENBOOT_ETC_CHECK_PATH/required.d/30_failing_check.sh 8 | cp testing_files/10_failing_check.sh $GREENBOOT_ETC_CHECK_PATH/required.d/40_failing_check.sh 9 | } 10 | 11 | @test "Test greenboot check with required scripts failing" { 12 | run $GREENBOOT_BIN_PATH check 13 | [ "$status" -ne 0 ] 14 | } 15 | 16 | @test "Test greenboot exits when one required script fails" { 17 | run $GREENBOOT_BIN_PATH check 18 | [ "$status" -ne 0 ] # Ensure greenboot exits with a non-zero status 19 | 20 | # Count occurrences of failing scripts in the output 21 | fail_count=$(echo "$output" | grep -E "FAILURE" | \ 22 | grep -c -E "10_failing_check.sh|20_failing_check.sh|30_failing_check.sh|40_failing_check.sh") 23 | 24 | # Ensure only one failing script is reported 25 | [ "$fail_count" -eq 1 ] 26 | } 27 | 28 | function teardown() { 29 | rm $GREENBOOT_DEFAULT_CHECK_PATH/required.d/10_failing_check.sh 30 | rm $GREENBOOT_DEFAULT_CHECK_PATH/required.d/20_failing_check.sh 31 | rm $GREENBOOT_ETC_CHECK_PATH/required.d/30_failing_check.sh 32 | rm $GREENBOOT_ETC_CHECK_PATH/required.d/40_failing_check.sh 33 | } 34 | -------------------------------------------------------------------------------- /tests/grub2_set_counter.bats: -------------------------------------------------------------------------------- 1 | load common.bash 2 | 3 | MAX_BOOT_ATTEMPTS=2 4 | MAX_BOOT_ATTEMPTS_DEFAULT=3 5 | MAX_BOOT_ATTEMPTS_ENV_VAR=4 6 | MAX_BOOT_ATTEMPTS_CONFIG_FILE=5 7 | 8 | CONFIG_FILE_PATH=$GREENBOOT_ETC_ROOT_PATH/greenboot.conf 9 | 10 | @test "Test Grub2 set counter with default values" { 11 | $GRUB2_SET_COUNTER_BIN_PATH 12 | run $GRUB2_EDITENV list 13 | [[ "$output" == *"boot_counter=$MAX_BOOT_ATTEMPTS_DEFAULT"* ]] 14 | [[ "$output" == *"boot_success=0"* ]] 15 | } 16 | 17 | @test "Test Grub2 set counter with input variable" { 18 | $GRUB2_SET_COUNTER_BIN_PATH $MAX_BOOT_ATTEMPTS 19 | run $GRUB2_EDITENV list 20 | [[ "$output" == *"boot_counter=$MAX_BOOT_ATTEMPTS"* ]] 21 | [[ "$output" == *"boot_success=0"* ]] 22 | } 23 | 24 | @test "Test Grub2 set counter with env variable" { 25 | GREENBOOT_MAX_BOOT_ATTEMPTS=$MAX_BOOT_ATTEMPTS_ENV_VAR $GRUB2_SET_COUNTER_BIN_PATH 26 | run $GRUB2_EDITENV list 27 | [[ "$output" == *"boot_counter=$MAX_BOOT_ATTEMPTS_ENV_VAR"* ]] 28 | [[ "$output" == *"boot_success=0"* ]] 29 | } 30 | 31 | @test "Test Grub2 set counter with config file" { 32 | echo GREENBOOT_MAX_BOOT_ATTEMPTS=$MAX_BOOT_ATTEMPTS_CONFIG_FILE >> $CONFIG_FILE_PATH 33 | $GRUB2_SET_COUNTER_BIN_PATH 34 | run $GRUB2_EDITENV list 35 | [[ "$output" == *"boot_counter=$MAX_BOOT_ATTEMPTS_CONFIG_FILE"* ]] 36 | [[ "$output" == *"boot_success=0"* ]] 37 | } 38 | 39 | function teardown() { 40 | $GRUB2_EDITENV - unset boot_counter 41 | $GRUB2_EDITENV - unset boot_success 42 | rm -f $CONFIG_FILE_PATH 43 | } 44 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing a new version 2 | ======================= 3 | 4 | We will use the `v0.15.8` release [#202](https://github.com/fedora-iot/greenboot/pull/202) as an example of how to release a new 5 | greenboot version: 6 | 7 | * Fork the repo and create a new branch for the new release: 8 | 9 | ```bash 10 | gh repo fork fedora-iot/greenboot --clone --remote 11 | git pull upstream main 12 | git checkout -b prepare-v0.15.8 13 | ``` 14 | 15 | * Update the `greenboot.spec` file and set the new version: `rpmdev-bumpspec -n 0.15.8 greenboot.spec` 16 | * Update anything required for the new RPM 17 | * Update the changelog section of the spec file 18 | * Commit all the changes and create a PR (see #738 with all the changes described 19 | above): 20 | 21 | ```bash 22 | git add greenboot.spec # add anything else needed 23 | git commit -s -m "chore: bump for 0.15.8 release" -m "Prepare for the 0.15.8 release." 24 | gh pr create 25 | ``` 26 | 27 | * Once all the tests pass and the PR is merged, tag and sign the release: 28 | 29 | ```bash 30 | git tag -a -s v0.15.8 31 | git push upstream v0.15.8 32 | ``` 33 | 34 | * Using the webui, open the [Releases](https://github.com/fedora-iot/greenboot/releases) 35 | page and click the "Draft a new release" button in the middle of the page. From 36 | there you can choose the `v0.15.8` tag you created in the previous step. 37 | * Use the version as the "Release title" and keep the format i.e. "v0.15.8". 38 | * In the description add in any release notes or click "Generate release notes". 39 | When satisfied, click the "Save draft" or "Publish release" button at the bottom of the page. 40 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot-status: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | IFS=$'\n\t' 4 | 5 | status="" 6 | 7 | # healthcheck logs from current boot 8 | healthcheck_info="$(journalctl -u greenboot-healthcheck.service -p 2 -b -0 -o cat)" || true 9 | if [ -n "$healthcheck_info" ]; then 10 | status+="$healthcheck_info"$'\n' 11 | fi 12 | 13 | if [ "$(systemctl is-active boot-complete.target)" = "active" ]; then 14 | # greenboot logs (post healthchecks) from current boot 15 | greenboot_runner_info="$(journalctl -u greenboot-task-runner.service -p 5 -b -0 -o cat)" || true 16 | if [ -n "$greenboot_runner_info" ]; then 17 | status+="$greenboot_runner_info"$'\n' 18 | fi 19 | else 20 | # redboot logs (post healthchecks) from current boot 21 | redboot_runner_info="$(journalctl -u redboot-task-runner.service -p 0 -b -0 -o cat)" || true 22 | if [ -n "$redboot_runner_info" ]; then 23 | status+="$redboot_runner_info"$'\n' 24 | fi 25 | # redboot-auto-reboot.service logs (post redboot-task-runner.service) from current boot 26 | redboot_reboot_info="$(journalctl -u redboot-auto-reboot.service -p 1 -b -0 -o cat)" || true 27 | if [ -n "$redboot_reboot_info" ]; then 28 | status+="$redboot_reboot_info"$'\n' 29 | fi 30 | fi 31 | 32 | if [ -z "$status" ]; then 33 | status=$'WARNING: No greenboot logs were found!\n' 34 | fi 35 | 36 | # TODO: Show initial/max and current boot_counter values in status 37 | # If this is a fallback boot (i.e. the countdown has reached its end), show logs from previous boot 38 | fallback_info="$(journalctl -u greenboot-rpm-ostree-grub2-check-fallback.service -b -0 -p 3 -o cat)" || true 39 | if [ -n "$fallback_info" ]; then 40 | status+="$fallback_info"$'\n' 41 | fi 42 | 43 | echo -n "$status" | tee /run/motd.d/boot-status 44 | -------------------------------------------------------------------------------- /.packit.yaml: -------------------------------------------------------------------------------- 1 | upstream_project_url: https://github.com/fedora-iot/greenboot 2 | 3 | specfile_path: greenboot.spec 4 | files_to_sync: 5 | - greenboot.spec 6 | - .packit.yaml 7 | upstream_package_name: greenboot 8 | upstream_tag_template: v{version} 9 | downstream_package_name: greenboot 10 | 11 | packages: 12 | greenboot-fedora: 13 | downstream_package_name: greenboot 14 | upstream_package_name: greenboot 15 | greenboot-centos: 16 | downstream_package_name: greenboot 17 | upstream_package_name: greenboot 18 | pkg_tool: centpkg 19 | 20 | jobs: 21 | 22 | # Fedora jobs 23 | 24 | - &greenboot_copr_build_fedora 25 | job: copr_build 26 | packages: [greenboot-fedora] 27 | trigger: pull_request 28 | targets: ["fedora-latest-stable", "fedora-latest", "fedora-rawhide"] 29 | 30 | - <<: *greenboot_copr_build_fedora 31 | trigger: commit 32 | branch: main 33 | owner: "@fedora-iot" 34 | project: fedora-iot 35 | 36 | - job: sync_from_downstream 37 | trigger: commit 38 | 39 | - job: propose_downstream 40 | trigger: release 41 | packages: [greenboot-fedora] 42 | dist_git_branches: ["fedora-development", "fedora-latest-stable"] 43 | 44 | - job: koji_build 45 | trigger: commit 46 | allowed_pr_authors: [all_committers] 47 | dist_git_branches: ["fedora-development", "fedora-latest-stable"] 48 | 49 | - job: bodhi_update 50 | trigger: commit 51 | allowed_builders: [all_committers] 52 | dist_git_branches: ["fedora-development", "fedora-latest-stable"] 53 | 54 | # CentOS jobs 55 | 56 | - &greenboot_copr_build_centos 57 | job: copr_build 58 | packages: [greenboot-centos] 59 | trigger: pull_request 60 | targets: ["centos-stream-9", "centos-stream-10"] 61 | 62 | - <<: *greenboot_copr_build_centos 63 | trigger: commit 64 | branch: main 65 | owner: "@fedora-iot" 66 | project: fedora-iot 67 | -------------------------------------------------------------------------------- /usr/lib/greenboot/check/required.d/01_repository_dns_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | REPOS_DIRECTORY=/etc/ostree/remotes.d 5 | DOMAINS_WITH_PROBLEMS=() 6 | 7 | get_domain_names_from_platform_urls() { 8 | DOMAIN_NAMES=$(grep -P -ho 'http[s]?\:\/\/[a-zA-Z0-9./-]+' $REPOS_DIRECTORY/* \ 9 | | grep -v -P '.*[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' \ 10 | | awk -F:// '{print $2}' \ 11 | | awk -F/ 'BEGIN{OFS="\n"}{print $1}' \ 12 | | sort | uniq) 13 | if [[ -z $DOMAIN_NAMES ]]; then 14 | echo "No domain names have been found" 15 | fi 16 | } 17 | 18 | get_dns_resolution_from_domain_names() { 19 | # Check if each domain name resolve into at least 1 IP 20 | # If it doesn't, add it to DOMAINS_WITH_PROBLEMS 21 | for line in $DOMAIN_NAMES; do 22 | NUMBER_OF_IPS_PER_DOMAIN=$(getent hosts "$line" | wc -l) 23 | if [[ $NUMBER_OF_IPS_PER_DOMAIN -eq 0 ]]; then 24 | DOMAINS_WITH_PROBLEMS+=( "$line" ) 25 | fi 26 | done 27 | } 28 | 29 | assert_dns_resolution_result() { 30 | # If the number of domains with problems is 0, everything's good 31 | # If it's not 0, we exit with errors and print the domains 32 | if [[ ${#DOMAINS_WITH_PROBLEMS[@]} -eq 0 ]]; then 33 | echo "All domains have resolved correctly" 34 | exit 0 35 | else 36 | echo "The following repository domains haven't responded properly to DNS queries:" 37 | echo "${DOMAINS_WITH_PROBLEMS[*]}" 38 | exit 1 39 | fi 40 | } 41 | 42 | if [[ ! -d $REPOS_DIRECTORY ]]; then 43 | echo "${REPOS_DIRECTORY} doesn't exist" 44 | exit 1 45 | fi 46 | 47 | if [ -z "$(ls -A $REPOS_DIRECTORY)" ]; then 48 | echo "${REPOS_DIRECTORY} is empty, skipping check" 49 | exit 0 50 | fi 51 | 52 | get_domain_names_from_platform_urls 53 | if [[ -n $DOMAIN_NAMES ]]; then 54 | get_dns_resolution_from_domain_names 55 | assert_dns_resolution_result 56 | fi 57 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot-rpm-ostree-grub2-check-fallback: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | source /usr/libexec/greenboot/greenboot-boot-remount 5 | 6 | function attempt_rollback { 7 | # Check if the bootc command is available 8 | if command -v bootc &> /dev/null; then 9 | status_type=$(bootc status --booted --json 2>/dev/null | jq -r .status.type 2>/dev/null) 10 | if [ "$status_type" == "bootcHost" ]; then 11 | echo "<3> On a bootc managed host, attempting rollback with bootc." 12 | bootc rollback 13 | echo "<3>FALLBACK BOOT DETECTED! Default bootc deployment has been rolled back." 14 | return 15 | fi 16 | echo "<3> Not on a bootc managed host, trying rpm-ostree rollback next." 17 | fi 18 | # Check if its ostree based os 19 | if [ -f /run/ostree-booted ]; then 20 | rpm-ostree rollback 21 | echo "<3>FALLBACK BOOT DETECTED! Default rpm-ostree deployment has been rolled back." 22 | return 23 | fi 24 | echo "<3>Rollback is only supported in ostree or bootc based os." 25 | return 26 | } 27 | 28 | # Determine if the current boot is a fallback boot 29 | # If booted into fallback deployment, clean up bootloader entries (rollback) 30 | if grub2-editenv list | grep -q "^boot_counter=-1$"; then 31 | # Logs from previous boot may be unavailable on systems without internal RTC; defaulting to empty string 32 | prev_logs="$(journalctl -u greenboot-healthcheck.service -p 2 -b -1 -o cat)" || true 33 | attempt_rollback 34 | if [ -n "$prev_logs" ]; then 35 | echo "<3>Health check logs from previous boot:" 36 | echo "<3>$prev_logs" 37 | fi 38 | 39 | remount_boot_rw 40 | 41 | if ! /usr/bin/grub2-editenv - unset boot_counter; then 42 | # If the above command fails, remount /boot as read-only and exit with failure 43 | remount_boot_ro 44 | exit 1 45 | fi 46 | fi 47 | 48 | # Remount /boot as read-only if it was mounted as read-write 49 | remount_boot_ro 50 | -------------------------------------------------------------------------------- /usr/lib/greenboot/check/required.d/02_watchdog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | source_configuration_file() { 5 | GREENBOOT_CONFIGURATION_FILE=/etc/greenboot/greenboot.conf 6 | if test -f "$GREENBOOT_CONFIGURATION_FILE"; then 7 | # shellcheck source=etc/greenboot/greenboot.conf 8 | source $GREENBOOT_CONFIGURATION_FILE 9 | fi 10 | } 11 | 12 | set_grace_period() { 13 | DEFAULT_GRACE_PERIOD=24 # default to 24 hours 14 | 15 | if [ -n "$GREENBOOT_WATCHDOG_GRACE_PERIOD" ]; then 16 | GRACE_PERIOD=$GREENBOOT_WATCHDOG_GRACE_PERIOD 17 | else 18 | GRACE_PERIOD=$DEFAULT_GRACE_PERIOD 19 | fi 20 | } 21 | 22 | check_if_there_is_a_watchdog() { 23 | if wdctl 2>/dev/null ; then 24 | return 0 25 | else 26 | return 1 27 | fi 28 | } 29 | 30 | check_if_current_boot_is_wd_triggered() { 31 | if check_if_there_is_a_watchdog ; then 32 | WDCTL_OUTPUT=$(wdctl --flags-only --noheadings | grep -c '1$' || true) 33 | if [ "$WDCTL_OUTPUT" -gt 0 ]; then 34 | # This means the boot was watchdog triggered 35 | # TO-DO: maybe do a rollback here? 36 | echo "Watchdog triggered after recent update" 37 | exit 1 38 | fi 39 | else 40 | # There's no watchdog, so nothing to be done here 41 | exit 0 42 | fi 43 | } 44 | 45 | # This is in order to test check_if_current_boot_is_wd_triggered 46 | # function within a container 47 | if [ "${1}" != "--source-only" ]; then 48 | if ! check_if_there_is_a_watchdog ; then 49 | echo "No watchdog on the system, skipping check" 50 | exit 0 51 | fi 52 | 53 | source_configuration_file 54 | if [ "${GREENBOOT_WATCHDOG_CHECK_ENABLED,,}" != "true" ]; then 55 | echo "Watchdog check is disabled" 56 | exit 0 57 | fi 58 | 59 | set_grace_period 60 | 61 | SECONDS_IN_AN_HOUR=$((60 * 60)) 62 | LAST_DEPLOYMENT_TIMESTAMP=$(rpm-ostree status --json | jq .deployments[0].timestamp) 63 | 64 | HOURS_SINCE_LAST_UPDATE=$((($(date +%s) - "$LAST_DEPLOYMENT_TIMESTAMP") / SECONDS_IN_AN_HOUR)) 65 | if [ "$HOURS_SINCE_LAST_UPDATE" -lt "$GRACE_PERIOD" ]; then 66 | check_if_current_boot_is_wd_triggered 67 | else 68 | exit 0 69 | fi 70 | fi 71 | -------------------------------------------------------------------------------- /development/README.md: -------------------------------------------------------------------------------- 1 | # How to develop Greenboot 2 | 3 | ## How to test your changes 4 | ### Container 5 | Ideal for quick development and making sure GitHub tests are going to pass but before creating a pull request, [try it out first in a VM with Fedora IoT and/or RHEL For Edge](#fedora-iot-or-rfe) 6 | 7 | Comment the line `ENTRYPOINT [ "/bin/bash", "launch_all_tests.sh" ]` in Dockerfile. 8 | 9 | Add this to a `docker-compose.yml` file: 10 | 11 | ``` yaml 12 | version: "3.8" 13 | services: 14 | greenboot: 15 | privileged: true 16 | build: . 17 | container_name: greenboot 18 | image: greenboot 19 | volumes: 20 | - /run/systemd/journal:/run/systemd/journal 21 | working_dir: /testing 22 | command: sleep infinity 23 | ``` 24 | 25 | 26 | Run this script 27 | ``` bash 28 | #!/bin/bash 29 | set -e 30 | 31 | # Bring down the previous environment in case there was one 32 | docker-compose down 33 | 34 | docker-compose up --build -d 35 | docker exec -it greenboot /bin/bash 36 | docker-compose down 37 | ``` 38 | 39 | ### Fedora IoT or RFE: 40 | As `/usr` is mounted as a read-only directory: 41 | 42 | - If you want to test health checks, place them under `/etc/greenboot/check/required.d` or `/etc/greenboot/check/wanted.d`. I personally would go for `wanted.d` first to make sure that you don't end up in a full loop of reboots in case your script goes wrong. 43 | 44 | - If you’re editing any of the services under `/usr/lib` or `/usr/libexec`, you have two options: 45 | - place the updated file under `/etc/systemd/system/{same_name_as_in_usr_lib} `and systemd will take this file with more priority. 46 | - Advanced version: unlock the deployment and add your files in the proper `/usr` directory. 47 | 48 | ## Unit testing 49 | Health checks, when possible, have been unit-tested with [BATS](https://bats-core.readthedocs.io/en/stable/writing-tests.html). These tests are placed under the [`tests` folder](https://github.com/fedora-iot/greenboot/blob/main/tests). 50 | 51 | Every file with the `.bats` extension will be tested. 52 | For declaring/using common environment variables, use [`common.bash`](https://github.com/fedora-iot/greenboot/blob/main/tests/common.bash). You can source them in your BATS file. 53 | If you need to add a file, please use [`tests/testing_files` folder](https://github.com/fedora-iot/greenboot/blob/main/tests/testing_files). -------------------------------------------------------------------------------- /tests/key/ostree_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAYEAs8aOXRHEviwyvzhQHx6Ou+NBMqAzPGmGgpAWpWtBZ1nzDEzquz0H 4 | tOGoQdHhS0tyJULSuW6YM8QXRRlaherk+c+IYWj40HnNBt/gQ+T8aOlfksUoSmaPmTRlNx 5 | 1axfadle0B9GBUI+QfBegInAN32IdgVVBRAkz7jKcuQOdrn1rtK+MThZm1O0iIUUEQEM9r 6 | uMo16gbK6WKbQL8s86DeII1ITZ8Dh0j7LxaVqARZFpqp9MrYB4N1b/Q5R1vuOT9NkB9VBX 7 | nV0djKi58ESN9KqrWjpFUvvaxP3YZ0flz6Rxxvob9k4DAwLkNyCuwG+Zv8gmze93MRYnx+ 8 | C0k9L1RtyceOcZ/hk3hx4X5AHwUgrcmutfdY4sW6sqL6UbpwgSnsSOXH0T7VjorTN5uzKt 9 | uq+y6aGzDPqDi1JuKdCijSLYVnJivbWMF1krhhn1EPmPILexNBtOmRmwjxzq8SfJWEKl7I 10 | n9OKYjjTwP3PR6uNzvfSjzRkoTjQkvaDxXTi0U91AAAFiBiBlykYgZcpAAAAB3NzaC1yc2 11 | EAAAGBALPGjl0RxL4sMr84UB8ejrvjQTKgMzxphoKQFqVrQWdZ8wxM6rs9B7ThqEHR4UtL 12 | ciVC0rlumDPEF0UZWoXq5PnPiGFo+NB5zQbf4EPk/GjpX5LFKEpmj5k0ZTcdWsX2nZXtAf 13 | RgVCPkHwXoCJwDd9iHYFVQUQJM+4ynLkDna59a7SvjE4WZtTtIiFFBEBDPa7jKNeoGyuli 14 | m0C/LPOg3iCNSE2fA4dI+y8WlagEWRaaqfTK2AeDdW/0OUdb7jk/TZAfVQV51dHYyoufBE 15 | jfSqq1o6RVL72sT92GdH5c+kccb6G/ZOAwMC5DcgrsBvmb/IJs3vdzEWJ8fgtJPS9UbcnH 16 | jnGf4ZN4ceF+QB8FIK3JrrX3WOLFurKi+lG6cIEp7Ejlx9E+1Y6K0zebsyrbqvsumhswz6 17 | g4tSbinQoo0i2FZyYr21jBdZK4YZ9RD5jyC3sTQbTpkZsI8c6vEnyVhCpeyJ/TimI408D9 18 | z0erjc730o80ZKE40JL2g8V04tFPdQAAAAMBAAEAAAGBAJIAmtQ5PwiXyqsD6AYuAgvTt7 19 | qO4q2YojZdIRc9MUPniH2f5i8klKKxdb3m30sQPebHC26vxAqeoatruNnz9/xuMLuzzgc6 20 | NGn13iQlz1zA0+7WEi/CdbMeG2mUfIk0Da2aa7D1nr/7X7qjRIK4SlffMjx3WyM8NDt59x 21 | WdHQmxhdbTt6IUQFyiPpuG9K5CVqEgEIM8+wRqId6GpNJD/sJ/G452qx3vBpiqheaLiXLT 22 | L15wctw/RlwjA3XR0npJzq6g066BMKYAnyT5wiCWisVFKxIudT0dphj4qmz74yC967U6ji 23 | AB9hZ8j9OhBDA/pypXbb781Lo4iBqM6auoZqbieOE+v9v6uDozmfxtQO5y2kFP7mBMsGwG 24 | L8oEfEPqWRTIXgvDVuBwoqdsmYzFP8SiyUDkOfcHcK924FzvyJ2LWlpNp9POXYdjTDm/oB 25 | k1xs9UkhCImavqUnKiAplLnzMNuYbLmofoesI/2LnuYc2BOx9zub3pru6AdGi6N2EWzQAA 26 | AMEAjbtZe+6sW5yepxKOEb0wOAmZhRGL7d50fuPuJYsljU+nQaI7NMAAZ+G3kAiaTd8npb 27 | A5MKZ2oW++YXjJNAD+Bifnz8LojjmCqCuJL52+VNwpordW23XxRNQdoEvdN516qLyMI4i7 28 | i1AxNbU73SUrSCkSb1ngrhiHHQz986VciRU4X13ENbUSzPYInLoP9wTt+5CUtgiQnxe5PF 29 | K125TVwnFaDMPUKHMhKFIkMJuAkCSKQT7n11wwO2uH9k48LxW6AAAAwQDelZf6e+Un0s1A 30 | jLTG+r8VLG2kClXtECrRQjlzwMfc+lKOB00jBEdBLgIg3h2ECPOqh3OD9S0SU2Ja/+zb0r 31 | wrkyzWdndhh0IOEJCqzdlJe9JBJEWwQTr9MH9s1ORyIA1XGp5GPMFIZhT393Zkichzfyoz 32 | aACW+glGfsw27THJvI5PGJkPiuzKvwGixRcBpf72bk/30Q/qkekErdxtT3Kea41X9QOYjb 33 | jwrWKHARpSmLP1dJrOmYh8HWzpAKghIX8AAADBAM7DunVgA++cHvG/8B5Nodb/S0D7MvOb 34 | OtaHMfUdIIiczwOEvyoRsPyAMEGMtAMHy2YIGQYsK6CZEYP7x3sOmDOocmwjcMpjywN0b/ 35 | g895R16d19MDzUU/SnfUsQgbEXV9KxBGa9mDiyoEiP/QduQU/YlJdQjQXvYjrTRzV6AHQo 36 | PCE/JIQfRcvypKQU1XOdLhSIFDbvAcVgvULwe08robTn2ooR/on4+MHOE0q9RyA4lKS7CQ 37 | 77li4GQONWrqyhCwAAABFvc3RyZWUtaW1hZ2UtdGVzdA== 38 | -----END OPENSSH PRIVATE KEY----- 39 | -------------------------------------------------------------------------------- /usr/libexec/greenboot/greenboot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | LC_ALL=C 5 | 6 | SCRIPTS_CHECK_PATHS=("/usr/lib/greenboot/check" "/etc/greenboot/check") 7 | SCRIPTS_GREEN_PATHS=("/usr/lib/greenboot/green.d" "/etc/greenboot/green.d") 8 | SCRIPTS_RED_PATHS=("/usr/lib/greenboot/red.d" "/etc/greenboot/red.d") 9 | 10 | source_configuration_file() { 11 | greenboot_configuration_file=/etc/greenboot/greenboot.conf 12 | if test -f "$greenboot_configuration_file"; then 13 | # shellcheck source=/etc/greenboot/greenboot.conf 14 | source $greenboot_configuration_file 15 | fi 16 | } 17 | 18 | source_configuration_file 19 | function is_disabled { 20 | healthcheck=$1 21 | for disabled_healthcheck in "${DISABLED_HEALTHCHECKS[@]}"; do 22 | if [ "${healthcheck}" == "${disabled_healthcheck}" ]; then 23 | return 0 24 | fi 25 | done 26 | return 1 27 | } 28 | 29 | script_runner () { 30 | local scripts_dir=$1; shift 31 | local mode=$1; shift 32 | local start_msg=$1; shift 33 | local required_hc_failed=false 34 | echo "$start_msg" 35 | for script in $(find "$scripts_dir" -name '*.sh' | sort); do 36 | if [[ "$required_hc_failed" == true && "$mode" == "strict" ]]; then 37 | echo "<5>'$(basename "$script")' was skipped due to a previous failure" 38 | elif is_disabled "$(basename "$script")"; then 39 | echo "<5>'$(basename "$script")' was skipped, as specified in config" 40 | else 41 | local return_code=0 42 | systemd-cat -t "$(basename "$script")" bash "$script" || return_code=$? 43 | if [ $return_code -ne 0 ]; then 44 | local failure_msg 45 | failure_msg="Script '$(basename "$script")' FAILURE (exit code '$return_code')" 46 | case "$mode" in 47 | "relaxed") 48 | echo "<2>$failure_msg. Continuing..." >&2 49 | ;; 50 | "strict") 51 | required_hc_failed=true 52 | echo "<0>$failure_msg. Continuing..." >&2 53 | esac 54 | else 55 | echo "<6>Script '$(basename "$script")' SUCCESS" 56 | fi 57 | fi 58 | done 59 | 60 | [[ $required_hc_failed == false ]] 61 | } 62 | 63 | case "$1" in 64 | "check") 65 | return_code=0 66 | for health_check_path in "${SCRIPTS_CHECK_PATHS[@]}"; do 67 | script_runner "$health_check_path/required.d" "strict" "Running Required Health Check Scripts..." || { 68 | return_code=1 69 | break 70 | } 71 | script_runner "$health_check_path/wanted.d" "relaxed" "Running Wanted Health Check Scripts..." 72 | done 73 | exit $return_code 74 | ;; 75 | "green") 76 | echo "<5>Boot Status is GREEN - Health Check SUCCESS" 77 | for green_path in "${SCRIPTS_GREEN_PATHS[@]}"; do 78 | script_runner "$green_path" "relaxed" "Running Green Scripts..." 79 | done 80 | ;; 81 | "red") 82 | echo "<0>Boot Status is RED - Health Check FAILURE!" 83 | for red_path in "${SCRIPTS_RED_PATHS[@]}"; do 84 | script_runner "$red_path" "relaxed" "Running Red Scripts..." 85 | done 86 | ;; 87 | *) 88 | echo "Unknown argument, exiting." >&2 89 | exit 1 90 | esac 91 | -------------------------------------------------------------------------------- /tests/files/fedora-39.json: -------------------------------------------------------------------------------- 1 | { 2 | "x86_64": [ 3 | { 4 | "name": "fedora", 5 | "baseurl": "https://dl.fedoraproject.org/pub/fedora/linux/development/39/Everything/x86_64/os/", 6 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGLykg8BEADURjKtgQpQNoluifXia+U3FuqGCTQ1w7iTqx1UvNhLX6tb9Qjy\nl/vjl1iXxucrd2JBnrT/21BdtaABhu2hPy7bpcGEkG8MDinAMZBzcyzHcS/JiGHZ\nd/YmMWQUgbDlApbxFSGWiXMgT0Js5QdcywHI5oiCmV0lkZ+khZ4PkVWmk6uZgYWf\nJOG5wp5TDPnoYXlA4CLb6hu2691aDm9b99XYqEjhbeIzS9bFQrdrQzRMKyzLr8NW\ns8Pq2tgyzu8txlWdBXJyAMKldTPstqtygLL9UUdo7CIQQzWqeDbAnv+WdOmiI/hR\netbbwNV+thkLJz0WD90C2L3JEeUJX5Qa4oPvfNLDeCKmJFEFUTCEdm0AYoQDjLJQ\n3d3q9M09thXO/jYM0cSnJDclssLNsNWfjJAerLadLwNnYRuralw7f74QSLYdJAJU\nSFShBlctWKnlhQ7ehockqtgXtWckkqPZZjGiMXwHde9b9Yyi+VqtUQWxSWny+9g9\n6tcoa3AdnmpqSTHQxYajD0EGXJ0z0NXfqxkI0lo8UxzypEBy4sARZ4XhTU73Zwk0\nLGhEUHlfyxXgRs6RRvM2UIoo+gou2M9rn/RWkhuHJNSfgrM0BmIBCjhjwGiS33Qh\nysLDWJMdch8lsu1fTmLEFQrOB93oieOJQ0Ysi5gQY8TOT+oZvVi9pSMJuwARAQAB\ntDFGZWRvcmEgKDM5KSA8ZmVkb3JhLTM5LXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQJOBBMBCAA4FiEE6PI5lvIyGGQMtEy+dc9axBi450wFAmLykg8CGw8FCwkI\nBwIGFQoJCAsCBBYCAwECHgECF4AACgkQdc9axBi450yd4w//ZtghbZX5KFstOdBS\nrcbBfCK9zmRvzeejzGl6lPKfqwx7OOHYxFlRa9MYLl8QG7Aq6yRRWzzEHiSb0wJw\nWXz5tbkAmV/fpS4wnb3FDArD44u317UAnaU+UlhgK1g62lwI2dGpvTSvohMBMeBY\nB5aBd+sLi3UtiSRM2XhxvxaWwr/oFLjKDukgrPQzeV3F/XdxGhSz/GZUVFVprcrB\nh/dIo4k0Za7YVRhlVM0coOIcKbcjxAK9CCZ8+jtdIh3/BN5zJ0RFMgqSsrWYWeft\nBI3KWLbyMfRwEtp7xSi17WXbRfsSoqwIVgP+RCSaAdVuiYs/GCRsT3ydYcDvutuJ\nYZoE53yczemM/1HZZFI04zI7KBsKm9NFH0o4K2nBWuowBm59iFvWHFpX6em54cq4\n45NwY01FkSQUqntfqCWFSowwFHAZM4gblOikq2B5zHoIntCiJlPGuaJiVSw9ZpEc\n+IEQfmXJjKGSkMbU9tmNfLR9skVQJizMTtoUQ12DWC+14anxnnR2hxnhUDAabV6y\nJ5dGeb/ArmxQj3IMrajdNwjuk9GMeMSSS2EMY8ryOuYwRbFhBOLhGAnmM5OOSUxv\nA4ipWraXDW0bK/wXI7yHMkc6WYrdV3SIXEqJBTp7npimv3JC+exWEbTLcgvV70FP\nX55M9nDtzUSayJuEcfFP2c9KQCE=\n=J4qZ\n-----END PGP PUBLIC KEY BLOCK-----\n", 7 | "check_gpg": true 8 | }, 9 | { 10 | "name": "source", 11 | "baseurl": "http://192.168.100.1/source/" 12 | } 13 | ], 14 | "aarch64": [ 15 | { 16 | "name": "fedora", 17 | "baseurl": "https://dl.fedoraproject.org/pub/fedora/linux/development/39/Everything/aarch64/os/", 18 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGLykg8BEADURjKtgQpQNoluifXia+U3FuqGCTQ1w7iTqx1UvNhLX6tb9Qjy\nl/vjl1iXxucrd2JBnrT/21BdtaABhu2hPy7bpcGEkG8MDinAMZBzcyzHcS/JiGHZ\nd/YmMWQUgbDlApbxFSGWiXMgT0Js5QdcywHI5oiCmV0lkZ+khZ4PkVWmk6uZgYWf\nJOG5wp5TDPnoYXlA4CLb6hu2691aDm9b99XYqEjhbeIzS9bFQrdrQzRMKyzLr8NW\ns8Pq2tgyzu8txlWdBXJyAMKldTPstqtygLL9UUdo7CIQQzWqeDbAnv+WdOmiI/hR\netbbwNV+thkLJz0WD90C2L3JEeUJX5Qa4oPvfNLDeCKmJFEFUTCEdm0AYoQDjLJQ\n3d3q9M09thXO/jYM0cSnJDclssLNsNWfjJAerLadLwNnYRuralw7f74QSLYdJAJU\nSFShBlctWKnlhQ7ehockqtgXtWckkqPZZjGiMXwHde9b9Yyi+VqtUQWxSWny+9g9\n6tcoa3AdnmpqSTHQxYajD0EGXJ0z0NXfqxkI0lo8UxzypEBy4sARZ4XhTU73Zwk0\nLGhEUHlfyxXgRs6RRvM2UIoo+gou2M9rn/RWkhuHJNSfgrM0BmIBCjhjwGiS33Qh\nysLDWJMdch8lsu1fTmLEFQrOB93oieOJQ0Ysi5gQY8TOT+oZvVi9pSMJuwARAQAB\ntDFGZWRvcmEgKDM5KSA8ZmVkb3JhLTM5LXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQJOBBMBCAA4FiEE6PI5lvIyGGQMtEy+dc9axBi450wFAmLykg8CGw8FCwkI\nBwIGFQoJCAsCBBYCAwECHgECF4AACgkQdc9axBi450yd4w//ZtghbZX5KFstOdBS\nrcbBfCK9zmRvzeejzGl6lPKfqwx7OOHYxFlRa9MYLl8QG7Aq6yRRWzzEHiSb0wJw\nWXz5tbkAmV/fpS4wnb3FDArD44u317UAnaU+UlhgK1g62lwI2dGpvTSvohMBMeBY\nB5aBd+sLi3UtiSRM2XhxvxaWwr/oFLjKDukgrPQzeV3F/XdxGhSz/GZUVFVprcrB\nh/dIo4k0Za7YVRhlVM0coOIcKbcjxAK9CCZ8+jtdIh3/BN5zJ0RFMgqSsrWYWeft\nBI3KWLbyMfRwEtp7xSi17WXbRfsSoqwIVgP+RCSaAdVuiYs/GCRsT3ydYcDvutuJ\nYZoE53yczemM/1HZZFI04zI7KBsKm9NFH0o4K2nBWuowBm59iFvWHFpX6em54cq4\n45NwY01FkSQUqntfqCWFSowwFHAZM4gblOikq2B5zHoIntCiJlPGuaJiVSw9ZpEc\n+IEQfmXJjKGSkMbU9tmNfLR9skVQJizMTtoUQ12DWC+14anxnnR2hxnhUDAabV6y\nJ5dGeb/ArmxQj3IMrajdNwjuk9GMeMSSS2EMY8ryOuYwRbFhBOLhGAnmM5OOSUxv\nA4ipWraXDW0bK/wXI7yHMkc6WYrdV3SIXEqJBTp7npimv3JC+exWEbTLcgvV70FP\nX55M9nDtzUSayJuEcfFP2c9KQCE=\n=J4qZ\n-----END PGP PUBLIC KEY BLOCK-----\n", 19 | "check_gpg": true 20 | }, 21 | { 22 | "name": "source", 23 | "baseurl": "http://192.168.100.1/source/" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /development/legacy/README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Fedora IoT 30 4 | 5 | In order to be able to easily check the motd output, the preferred way of testing is over ssh. 6 | 7 | ### Preparation 8 | ```bash 9 | # ssh into your machine 10 | 11 | # Ensure the following rpms are installed: 12 | # greenboot greenboot-status 13 | # otherwise install them: 14 | build=00876215 && \ 15 | curl https://copr-be.cloud.fedoraproject.org/results/lorbus/greenboot/fedora-30-x86_64/$build-greenboot/greenboot-0.7-1.fc30.noarch.rpm --output greenboot-0.7-1.fc30.noarch.rpm && \ 16 | curl https://copr-be.cloud.fedoraproject.org/results/lorbus/greenboot/fedora-30-x86_64/$build-greenboot/greenboot-grub2-0.7-1.fc30.noarch.rpm --output greenboot-grub2-0.7-1.fc30.noarch.rpm && \ 17 | curl https://copr-be.cloud.fedoraproject.org/results/lorbus/greenboot/fedora-30-x86_64/$build-greenboot/greenboot-reboot-0.7-1.fc30.noarch.rpm --output greenboot-reboot-0.7-1.fc30.noarch.rpm && \ 18 | curl https://copr-be.cloud.fedoraproject.org/results/lorbus/greenboot/fedora-30-x86_64/$build-greenboot/greenboot-rpm-ostree-grub2-0.7-1.fc30.noarch.rpm --output greenboot-rpm-ostree-grub2-0.7-1.fc30.noarch.rpm && \ 19 | curl https://copr-be.cloud.fedoraproject.org/results/lorbus/greenboot/fedora-30-x86_64/$build-greenboot/greenboot-status-0.7-1.fc30.noarch.rpm --output greenboot-status-0.7-1.fc30.noarch.rpm && \ 20 | sudo rpm-ostree override replace \ 21 | greenboot-0.7-1.fc30.noarch.rpm \ 22 | greenboot-grub2-0.7-1.fc30.noarch.rpm \ 23 | greenboot-reboot-0.7-1.fc30.noarch.rpm \ 24 | --remove=greenboot-ostree-grub2-0.6-1.fc30.noarch \ 25 | --install=greenboot-rpm-ostree-grub2-0.7-1.fc30.noarch.rpm \ 26 | --install=greenboot-status-0.7-1.fc30.noarch.rpm && \ 27 | sudo systemctl reboot 28 | 29 | # Enabling services, but let's hold off on enabling redboot-auto-reboot.service for a bit 30 | # so we don't get into reboot-looping through all our boot attempts (i.e. until boot_counter reaches 0) 31 | sudo systemctl enable \ 32 | greenboot-task-runner \ 33 | greenboot-healthcheck \ 34 | greenboot-rpm-ostree-grub2-check-fallback \ 35 | greenboot-grub2-set-counter \ 36 | greenboot-grub2-set-success \ 37 | greenboot-status \ 38 | redboot-task-runner && \ 39 | sudo systemctl reboot 40 | 41 | ``` 42 | 43 | ### Testing 44 | 45 | ```bash 46 | # Test success to complete checks 47 | # 48 | 49 | # When logging in via ssh, you should see the motd for a green boot status: 50 | # 51 | # Boot Status is GREEN - Health Check SUCCESS 52 | 53 | # Check all our logs 54 | sudo journalctl -b -0 \ 55 | -u boot-complete.target \ 56 | -u greenboot-task-runner \ 57 | -u greenboot-healthcheck \ 58 | -u greenboot-rpm-ostree-grub2-check-fallback \ 59 | -u greenboot-grub2-set-counter \ 60 | -u greenboot-grub2-set-success \ 61 | -u greenboot-status \ 62 | -u redboot-task-runner \ 63 | -u redboot-auto-reboot \ 64 | -u redboot.target 65 | 66 | # check grubenv variables 67 | sudo grub2-editenv list 68 | 69 | # the service that sets boot_success to 1 before reboot should be active: 70 | sudo systemctl is-active greenboot-grub2-set-success.service 71 | 72 | 73 | # Test failure to complete checks 74 | # 75 | 76 | # Install sanely failing health check unit to test red boot status behavior 77 | curl https://copr-be.cloud.fedoraproject.org/results/lorbus/greenboot/fedora-30-x86_64/00858207-greenboot-failing-unit/greenboot-failing-unit-1.0-1.fc30.noarch.rpm --output greenboot-failing-unit-1.0-1.fc30.noarch.rpm && \ 78 | sudo rpm-ostree install greenboot-failing-unit-1.0-1.fc30.noarch.rpm && \ 79 | sudo systemctl reboot 80 | 81 | # If greenboot-reboot is disabled, you should see the following when logging in: 82 | # 83 | # Boot Status is RED - Health Check FAILURE! 84 | # Script '10_failing_check.sh' FAILURE (exit code '1') 85 | 86 | # Check all our journal logs again 87 | sudo journalctl -b -0 \ 88 | -u boot-complete.target \ 89 | -u greenboot-task-runner \ 90 | -u greenboot-healthcheck \ 91 | -u greenboot-rpm-ostree-grub2-check-fallback \ 92 | -u greenboot-grub2-set-counter \ 93 | -u greenboot-grub2-set-success \ 94 | -u greenboot-status \ 95 | -u redboot-task-runner \ 96 | -u redboot-auto-reboot \ 97 | -u redboot.target 98 | 99 | # grubenv should contain: 100 | # boot_counter=2 101 | sudo grub2-editenv list 102 | 103 | # the service to set boot_success to 1 before reboot should be inactive (dead): 104 | sudo systemctl status greenboot-grub2-set-success.service 105 | 106 | # Let's enable the system to try all the remaining boot attempts at this failing deployment, 107 | # before finally booting back into the rollback deployment automatically. 108 | sudo systemctl enable redboot-auto-reboot && \ 109 | sudo systemctl reboot 110 | 111 | # After the remaining boot attempts have been tried, log in again and you should see: 112 | # 113 | # Boot Status is GREEN - Health Check SUCCESS 114 | # FALLBACK BOOT DETECTED! Default rpm-ostree deployment has been rolled back. 115 | # Health check logs from previous boot: 116 | # Script '10_failing_check.sh' FAILURE (exit code '1') 117 | 118 | # Check all our journal logs again 119 | sudo journalctl -b -0 \ 120 | -u boot-complete.target \ 121 | -u greenboot-task-runner \ 122 | -u greenboot-healthcheck \ 123 | -u greenboot-rpm-ostree-grub2-check-fallback \ 124 | -u greenboot-grub2-set-counter \ 125 | -u greenboot-grub2-set-success \ 126 | -u greenboot-status \ 127 | -u redboot-task-runner \ 128 | -u redboot-auto-reboot \ 129 | -u redboot.target 130 | 131 | # grubenv 132 | sudo grub2-editenv list 133 | 134 | # the service to set boot_success to 1 before reboot should be active again: 135 | sudo systemctl status greenboot-grub2-set-success.service 136 | 137 | ``` -------------------------------------------------------------------------------- /tests/greenboot-bootc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: greenboot_guest 3 | become: no 4 | vars: 5 | total_counter: "0" 6 | failed_counter: "0" 7 | 8 | 9 | tasks: 10 | # current target host's IP address 11 | - debug: var=ansible_all_ipv4_addresses 12 | - debug: var=ansible_facts['distribution_version'] 13 | - debug: var=ansible_facts['distribution'] 14 | - debug: var=ansible_facts['architecture'] 15 | 16 | - name: check bootc status 17 | command: bootc status 18 | ignore_errors: yes 19 | 20 | # case: check installed greenboot packages 21 | - name: greenboot should be installed 22 | block: 23 | - name: greenboot should be installed 24 | shell: rpm -qa | grep greenboot 25 | register: result_greenboot_packages 26 | 27 | - assert: 28 | that: 29 | - "'greenboot-0' in result_greenboot_packages.stdout" 30 | - "'greenboot-default-health-checks' in result_greenboot_packages.stdout" 31 | fail_msg: "greenboot is not installed" 32 | success_msg: "greenboot is installed" 33 | always: 34 | - set_fact: 35 | total_counter: "{{ total_counter | int + 1 }}" 36 | rescue: 37 | - name: failed count + 1 38 | set_fact: 39 | failed_counter: "{{ failed_counter | int + 1 }}" 40 | 41 | # case: check greenboot* services 42 | - name: a list of greenboot* service should be enabled 43 | block: 44 | - name: a list of greenboot* service should be enabled 45 | command: systemctl is-enabled greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-healthcheck greenboot-rpm-ostree-grub2-check-fallback greenboot-status greenboot-task-runner redboot-auto-reboot redboot-task-runner 46 | register: result_greenboot_service 47 | 48 | - assert: 49 | that: 50 | - result_greenboot_service.stdout == 'enabled\nenabled\nenabled\nenabled\ndisabled\ndisabled\nenabled\nenabled' 51 | fail_msg: "Some of greenboot* services are not enabled" 52 | success_msg: "All greenboot* services are enabled" 53 | always: 54 | - set_fact: 55 | total_counter: "{{ total_counter | int + 1 }}" 56 | rescue: 57 | - name: failed count + 1 58 | set_fact: 59 | failed_counter: "{{ failed_counter | int + 1 }}" 60 | when: (ansible_facts['distribution'] == 'Fedora') or 61 | (ansible_facts['distribution'] == 'CentOS' and ansible_facts ['distribution_version'] is version('10', '==')) or 62 | (ansible_facts['distribution'] == 'RedHat' and ansible_facts ['distribution_version'] is version('10.0', '==')) 63 | 64 | # case: check greenboot* services 65 | - name: a list of greenboot* service should be enabled 66 | block: 67 | - name: a list of greenboot* service should be enabled 68 | command: systemctl is-enabled greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-healthcheck greenboot-rpm-ostree-grub2-check-fallback greenboot-status greenboot-task-runner redboot-auto-reboot redboot-task-runner 69 | register: result_greenboot_service 70 | 71 | - assert: 72 | that: 73 | - result_greenboot_service.stdout == 'enabled\nenabled\nenabled\nenabled\nenabled\nenabled\nenabled\nenabled' 74 | fail_msg: "Some of greenboot* services are not enabled" 75 | success_msg: "All greenboot* services are enabled" 76 | always: 77 | - set_fact: 78 | total_counter: "{{ total_counter | int + 1 }}" 79 | rescue: 80 | - name: failed count + 1 81 | set_fact: 82 | failed_counter: "{{ failed_counter | int + 1 }}" 83 | when: (ansible_facts['distribution'] == 'CentOS' and ansible_facts ['distribution_version'] is version('9', '==')) or 84 | (ansible_facts['distribution'] == 'RedHat' and ansible_facts ['distribution_version'] is version('9.6', '==')) 85 | 86 | # case: check greenboot fall back log 87 | - name: fallback log should be found here 88 | block: 89 | - name: check boot-complete.target 90 | command: systemctl --no-pager status boot-complete.target 91 | become: yes 92 | register: result 93 | retries: 10 94 | delay: 60 95 | until: "'inactive' not in result.stdout" 96 | 97 | - name: fallback log should be found here 98 | command: journalctl -b -0 -u greenboot -u greenboot-healthcheck -u greenboot-rpm-ostree-grub2-check-fallback -u greenboot-grub2-set-counter -u greenboot-grub2-set-success -u greenboot-status -u redboot -u redboot-auto-reboot -u redboot.target 99 | become: yes 100 | register: result_greenboot_log 101 | 102 | - assert: 103 | that: 104 | - "'FALLBACK BOOT DETECTED! Default bootc deployment has been rolled back' in result_greenboot_log.stdout" 105 | - "'Script \\'00_required_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout" 106 | - "'Script \\'00_wanted_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout" 107 | - "'greenboot Health Checks Runner' in result_greenboot_log.stdout" 108 | - "'Mark boot as successful in grubenv' in result_greenboot_log.stdout" 109 | fail_msg: "Fallback log not found" 110 | success_msg: "Found fallback log" 111 | 112 | always: 113 | - set_fact: 114 | total_counter: "{{ total_counter | int + 1 }}" 115 | rescue: 116 | - name: failed count + 1 117 | set_fact: 118 | failed_counter: "{{ failed_counter | int + 1 }}" 119 | 120 | # case: check boot_success 121 | - name: grubenv variables should contain boot_success=1 122 | block: 123 | - name: grubenv variables should contain boot_success=1 124 | command: grub2-editenv list 125 | register: result_grubenv 126 | become: yes 127 | 128 | - assert: 129 | that: 130 | - "'boot_success=1' in result_grubenv.stdout" 131 | fail_msg: "Not found boot_success=1" 132 | success_msg: "Found boot_success=1" 133 | always: 134 | - set_fact: 135 | total_counter: "{{ total_counter | int + 1 }}" 136 | rescue: 137 | - name: failed count + 1 138 | set_fact: 139 | failed_counter: "{{ failed_counter | int + 1 }}" 140 | 141 | - assert: 142 | that: 143 | - failed_counter == "0" 144 | fail_msg: "Run {{ total_counter }} tests, but {{ failed_counter }} of them failed" 145 | success_msg: "Totally {{ total_counter }} test passed" 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # greenboot 2 | Generic Health Check Framework for systemd on [rpm-ostree](https://coreos.github.io/rpm-ostree/) based systems. 3 | 4 | ## Table of contents 5 | * [Installation](#installation) 6 | * [Usage](#usage) 7 | + [Health checks with bash scripts](#health-checks-with-bash-scripts) 8 | - [Health checks included with subpackage greenboot-default-health-checks](#health-checks-included-with-subpackage-greenboot\-default\-health\-checks) 9 | + [Health Checks with systemd services](#health-checks-with-systemd-services) 10 | - [Required Checks](#required-checks) 11 | - [Wanted Checks](#wanted-checks) 12 | + [Configuration](#configuration) 13 | * [How does it work](#how-does-it-work) 14 | * [Development](#development) 15 | 16 | ## Installation 17 | Greenboot is comprised of two packages: 18 | - `greenboot` itself, with all core functionalities: check provided scripts, reboot if these checks don't pass, rollback to previous deployment if rebooting hasn't solved the problem, etc. 19 | - `greenboot-default-health-checks`, a series of optional and curated health checks provided by Greenboot maintainers. 20 | 21 | In order to get a full Greenboot installation on Fedora Silverblue, Fedora IoT or Fedora CoreOS: 22 | ``` 23 | rpm-ostree install greenboot greenboot-default-health-checks 24 | 25 | systemctl reboot 26 | ``` 27 | 28 | ## Usage 29 | 30 | ### Health checks with bash scripts 31 | Place shell scripts representing *health checks* that **MUST NOT FAIL** in the `/etc/greenboot/check/required.d` directory. If any script in this folder exits with an error code, the boot will be declared as failed. Error message will appear in both MOTD and in `journalctl -u greenboot-healthcheck.service`. 32 | Place shell scripts representing *health checks* that **MAY FAIL** in the `/etc/greenboot/check/wanted.d` directory. Scripts in this folder can exit with an error code and the boot will not be declared as failed. Error message will appear in both MOTD and in `journalctl -u greenboot-healthcheck.service -b`. 33 | Place shell scripts you want to run *after* a boot has been declared **successful** (green) in `/etc/greenboot/green.d`. 34 | Place shell scripts you want to run *after* a boot has been declared **failed** (red) in `/etc/greenboot/red.d`. 35 | 36 | Unless greenboot is enabled by default in your distribution, enable it by running `systemctl enable greenboot-task-runner greenboot-healthcheck greenboot-status greenboot-loading-message greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-rpm-ostree-grub2-check-fallback redboot-auto-reboot redboot-task-runner`. 37 | It will automatically start during the next boot process and run its checks. 38 | 39 | When you `ssh` into the machine after that, a boot status message will be shown: 40 | 41 | ``` 42 | Boot Status is GREEN - Health Check SUCCESS 43 | ``` 44 | ``` 45 | Boot Status is RED - Health Check FAILURE! 46 | ``` 47 | 48 | Directory structure: 49 | ``` 50 | /etc 51 | └── greenboot 52 | ├── check 53 | │   ├── required.d 54 | │   └── wanted.d 55 | ├── green.d 56 | └── red.d 57 | ``` 58 | 59 | #### Health checks included with subpackage greenboot-default-health-checks 60 | These health checks are available in `/usr/lib/greenboot/check`, a read-only directory in rpm-ostree systems. If you find a bug in any of them or you have an improvement, please create a PR with such fix/feature and we'll review it and potentially include it. 61 | 62 | - **Check if repositories URLs are still DNS solvable**: This script is under `/usr/lib/greenboot/check/required.d/01_repository_dns_check.sh` and makes sure that DNS queries to repository URLs are still available. 63 | - **Check if update platforms are still reachable**: This script is under `/usr/lib/greenboot/check/wanted.d/01_update_platform_check.sh` and tries to connect and get a 2XX or 3XX HTTP code from the update platforms defined in `/etc/ostree/remotes.d`. 64 | - **Check if current boot has been triggered by hardware watchdog**: This script is under `/usr/lib/greenboot/check/required.d/02_watchdog.sh` and checks whether the current boot has been watchdog-triggered or not. If it is, but the reboot has occurred after a certain grace period (default of 24 hours, configurable via `GREENBOOT_WATCHDOG_GRACE_PERIOD=number_of_hours` in `/etc/greenboot/greenboot.conf`), Greenboot won't mark the current boot as red and won't rollback to the previous deployment. If has occurred within the grace period, at the moment the current boot will be marked as red, but Greenboot won't rollback to the previous deployment. It is enabled by default but it can be disabled by modifying `GREENBOOT_WATCHDOG_CHECK_ENABLED` in `/etc/greenboot/greenboot.conf` to `false`. 65 | 66 | ### Health Checks with systemd services 67 | Overall boot success is measured against `boot-complete.target`. 68 | Ordering of units can be achieved using standard systemd vocabulary. 69 | 70 | #### Required Checks 71 | Create a oneshot health check service unit that **MUST NOT FAIL**, e.g. `/etc/systemd/system/required-check.service`. Make sure it calls `redboot.target` when it fails (`OnFailure=redboot.target`). Run `systemctl enable required-check` to enable it. 72 | 73 | ``` 74 | [Unit] 75 | Description=Custom Required Health Check 76 | Before=boot-complete.target 77 | OnFailure=redboot.target 78 | OnFailureJobMode=fail 79 | 80 | [Service] 81 | Type=oneshot 82 | ExecStart=/usr/libexec/mytestsuite/required-check 83 | 84 | [Install] 85 | RequiredBy=boot-complete.target 86 | WantedBy=multi-user.target 87 | ``` 88 | 89 | #### Wanted Checks 90 | Create a oneshot health check service unit that **MAY FAIL**, e.g. `/etc/systemd/system/wanted-check.service`. Run `systemctl enable wanted-check` to enable it. 91 | 92 | ``` 93 | [Unit] 94 | Description=Custom Wanted Health Check 95 | Before=boot-complete.target 96 | 97 | [Service] 98 | Type=oneshot 99 | ExecStart=/usr/libexec/mytestsuite/wanted-check 100 | 101 | [Install] 102 | WantedBy=boot-complete.target 103 | WantedBy=multi-user.target 104 | ``` 105 | 106 | ### Configuration 107 | At the moment, it is possible to customize the following parameters via environment variables. These environment variables can be described as well in the config file `/etc/greenboot/greenboot.conf`: 108 | - **GREENBOOT_MAX_BOOT_ATTEMPTS**: Maximum number of boot attempts before declaring the deployment as problematic and rolling back to the previous one. 109 | - **GREENBOOT_WATCHDOG_CHECK_ENABLED**: Enables/disables *Check if current boot has been triggered by hardware watchdog* health check. More info on [Health checks included with subpackage greenboot-default-health-checks](#health-checks-included-with-subpackage-greenboot\-default\-health\-checks) section. 110 | - **GREENBOOT_WATCHDOG_GRACE_PERIOD**: Number of hours after an upgrade that we consider the new deployment as culprit of reboot. 111 | 112 | ## How does it work 113 | - `greenboot-rpm-ostree-grub2-check-fallback.service` runs **before** `greenboot-healthcheck.service` and checks whether the GRUB2 environment variable `boot_counter` is -1. 114 | - If it is -1, this would mean that the system is in a fallback deployment and would execute `rpm-ostree rollback` to go back to the previous, working deployment. 115 | - If `boot_counter` is not -1, nothing is done in this step. 116 | - `greenboot-healthcheck.service` runs **before** systemd's [boot-complete.target](https://www.freedesktop.org/software/systemd/man/systemd.special.html#boot-complete.target). It launches `/usr/libexec/greenboot/greenboot check`, which runs the `required.d` and `wanted.d` scripts. 117 | - If any script in the `required.d` folder fails, `redboot.target` is called. 118 | - It triggers `redboot-task-runner.service`, which launches `/usr/libexec/greenboot/greenboot red`. This will run the scripts in `red.d` folder. 119 | - After the above: 120 | - `greenboot-status.service` is run, creating the MOTD specifying which scripts have failed. 121 | - `redboot-auto-reboot.service` is run. It performs a series of checks to determine if there's a requirement for manual intervention. If there's not, it reboots the system. 122 | - If all scripts in `required.d` folder succeeded: 123 | - `boot-complete.target` is reached. 124 | - `greenboot-grub2-set-success.service` is run. It unsets `boot_counter` GRUB env var and sets `boot_success` GRUB env var to 1. 125 | - `greenboot-task-runner.service` launches `/usr/libexec/greenboot/greenboot green`, which runs the scripts in `green.d` folder, scripts that are meant to be run after a successful update. 126 | - `greenboot-status.service` is run, creating the MOTD with a success message. 127 | 128 | ## Development 129 | Please refer to [development/README.md](https://github.com/fedora-iot/greenboot/blob/main/development/README.md) file. 130 | -------------------------------------------------------------------------------- /tests/greenboot-rpm-ostree.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: ostree_guest 3 | become: no 4 | vars: 5 | total_counter: "0" 6 | failed_counter: "0" 7 | 8 | tasks: 9 | 10 | # current target host's IP address 11 | - debug: var=ansible_all_ipv4_addresses 12 | - debug: var=ansible_facts['distribution_version'] 13 | - debug: var=ansible_facts['distribution'] 14 | - debug: var=ansible_facts['architecture'] 15 | 16 | # case: check installed greenboot packages 17 | - name: greenboot should be installed 18 | block: 19 | - name: greenboot should be installed 20 | shell: rpm -qa | grep greenboot 21 | register: result_greenboot_packages 22 | 23 | - assert: 24 | that: 25 | - "'greenboot-0' in result_greenboot_packages.stdout" 26 | - "'greenboot-default-health-checks' in result_greenboot_packages.stdout" 27 | fail_msg: "greenboot is not installed" 28 | success_msg: "greenboot is installed" 29 | always: 30 | - set_fact: 31 | total_counter: "{{ total_counter | int + 1 }}" 32 | rescue: 33 | - name: failed count + 1 34 | set_fact: 35 | failed_counter: "{{ failed_counter | int + 1 }}" 36 | 37 | # case: check greenboot* services 38 | - name: a list of greenboot* service should be enabled 39 | block: 40 | - name: a list of greenboot* service should be enabled 41 | command: systemctl is-enabled greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-healthcheck greenboot-rpm-ostree-grub2-check-fallback greenboot-status greenboot-task-runner redboot-auto-reboot redboot-task-runner 42 | register: result_greenboot_service 43 | 44 | - assert: 45 | that: 46 | - result_greenboot_service.stdout == 'enabled\nenabled\nenabled\nenabled\ndisabled\ndisabled\nenabled\nenabled' 47 | fail_msg: "Some of greenboot* services are not enabled" 48 | success_msg: "All greenboot* services are enabled" 49 | always: 50 | - set_fact: 51 | total_counter: "{{ total_counter | int + 1 }}" 52 | rescue: 53 | - name: failed count + 1 54 | set_fact: 55 | failed_counter: "{{ failed_counter | int + 1 }}" 56 | when: (ansible_facts['distribution'] == 'Fedora') or 57 | (ansible_facts['distribution'] == 'CentOS' and ansible_facts ['distribution_version'] is version('10', '==')) or 58 | (ansible_facts['distribution'] == 'RedHat' and ansible_facts ['distribution_version'] is version('10.0', '==')) 59 | 60 | # case: check greenboot* services 61 | - name: a list of greenboot* service should be enabled 62 | block: 63 | - name: a list of greenboot* service should be enabled 64 | command: systemctl is-enabled greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-healthcheck greenboot-rpm-ostree-grub2-check-fallback greenboot-status greenboot-task-runner redboot-auto-reboot redboot-task-runner 65 | register: result_greenboot_service 66 | 67 | - assert: 68 | that: 69 | - result_greenboot_service.stdout == 'enabled\nenabled\nenabled\nenabled\nenabled\nenabled\nenabled\nenabled' 70 | fail_msg: "Some of greenboot* services are not enabled" 71 | success_msg: "All greenboot* services are enabled" 72 | always: 73 | - set_fact: 74 | total_counter: "{{ total_counter | int + 1 }}" 75 | rescue: 76 | - name: failed count + 1 77 | set_fact: 78 | failed_counter: "{{ failed_counter | int + 1 }}" 79 | when: (ansible_facts['distribution'] == 'CentOS' and ansible_facts ['distribution_version'] is version('9', '==')) or 80 | (ansible_facts['distribution'] == 'RedHat' and ansible_facts ['distribution_version'] is version('9.6', '==')) 81 | 82 | # case: check rollback function if boot error found 83 | - name: install sanely failing health check unit to test red boot status behavior 84 | block: 85 | - name: install sanely failing health check unit to test red boot status behavior 86 | command: rpm-ostree install --cache-only https://kite-webhook-prod.s3.amazonaws.com/greenboot-failing-unit-1.0-1.el8.noarch.rpm --reboot 87 | become: yes 88 | ignore_errors: yes 89 | ignore_unreachable: yes 90 | 91 | - name: delay 60 seconds before reboot to make system stable 92 | pause: 93 | seconds: 60 94 | delegate_to: 127.0.0.1 95 | 96 | - name: wait for connection to become reachable/usable 97 | wait_for_connection: 98 | delay: 30 99 | 100 | - name: waits until instance is reachable 101 | wait_for: 102 | host: "{{ ansible_all_ipv4_addresses[0] }}" 103 | port: 22 104 | search_regex: OpenSSH 105 | delay: 10 106 | register: result_rollback 107 | until: result_rollback is success 108 | retries: 6 109 | delay: 10 110 | 111 | - assert: 112 | that: 113 | - result_rollback is succeeded 114 | fail_msg: "Rollback failed" 115 | success_msg: "Rollback success" 116 | always: 117 | - set_fact: 118 | total_counter: "{{ total_counter | int + 1 }}" 119 | rescue: 120 | - name: failed count + 1 121 | set_fact: 122 | failed_counter: "{{ failed_counter | int + 1 }}" 123 | 124 | # case: check ostree commit correctly updated 125 | - name: get deployed ostree commit 126 | shell: rpm-ostree status --json | jq -r '.deployments[0].checksum' 127 | register: result_commit 128 | 129 | - name: make a json result 130 | set_fact: 131 | deploy_commit: "{{ result_commit.stdout }}" 132 | 133 | # case: check ostree commit after rollback 134 | - name: check ostree commit after rollback 135 | block: 136 | - name: check ostree commit after rollback 137 | shell: rpm-ostree status --json | jq -r '.deployments[0].checksum' 138 | register: result_commit 139 | 140 | - assert: 141 | that: 142 | - deploy_commit == ostree_commit 143 | fail_msg: "Not rollbackto last commit" 144 | success_msg: "Rollback success" 145 | always: 146 | - set_fact: 147 | total_counter: "{{ total_counter | int + 1 }}" 148 | rescue: 149 | - name: failed count + 1 150 | set_fact: 151 | failed_counter: "{{ failed_counter | int + 1 }}" 152 | when: result_rollback is succeeded 153 | 154 | # case: check greenboot fall back log 155 | - name: fallback log should be found here 156 | block: 157 | - name: check boot-complete.target 158 | command: systemctl --no-pager status boot-complete.target 159 | become: yes 160 | register: result 161 | retries: 10 162 | delay: 60 163 | until: "'inactive' not in result.stdout" 164 | 165 | - name: fallback log should be found here 166 | command: journalctl -b -0 -u greenboot -u greenboot-healthcheck -u greenboot-rpm-ostree-grub2-check-fallback -u greenboot-grub2-set-counter -u greenboot-grub2-set-success -u greenboot-status -u redboot -u redboot-auto-reboot -u redboot.target 167 | become: yes 168 | register: result_greenboot_log 169 | 170 | - assert: 171 | that: 172 | - "'FALLBACK BOOT DETECTED! Default rpm-ostree deployment has been rolled back' in result_greenboot_log.stdout" 173 | - "'Script \\'00_required_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout" 174 | - "'Script \\'00_wanted_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout" 175 | - "'greenboot Health Checks Runner' in result_greenboot_log.stdout" 176 | - "'Mark boot as successful in grubenv' in result_greenboot_log.stdout" 177 | fail_msg: "Fallback log not found" 178 | success_msg: "Found fallback log" 179 | 180 | always: 181 | - set_fact: 182 | total_counter: "{{ total_counter | int + 1 }}" 183 | rescue: 184 | - name: failed count + 1 185 | set_fact: 186 | failed_counter: "{{ failed_counter | int + 1 }}" 187 | 188 | # case: check grubenv variables again 189 | - name: grubenv variables should contain boot_success=1 190 | block: 191 | - name: grubenv variables should contain boot_success=1 192 | command: grub2-editenv list 193 | register: result_grubenv 194 | become: yes 195 | 196 | - assert: 197 | that: 198 | - "'boot_success=1' in result_grubenv.stdout" 199 | fail_msg: "Not found boot_success=1" 200 | success_msg: "Found boot_success=1" 201 | always: 202 | - set_fact: 203 | total_counter: "{{ total_counter | int + 1 }}" 204 | rescue: 205 | - name: failed count + 1 206 | set_fact: 207 | failed_counter: "{{ failed_counter | int + 1 }}" 208 | when: result_rollback is succeeded 209 | 210 | - assert: 211 | that: 212 | - failed_counter == "0" 213 | fail_msg: "Run {{ total_counter }} tests, but {{ failed_counter }} of them failed" 214 | success_msg: "Totally {{ total_counter }} test passed" 215 | -------------------------------------------------------------------------------- /tests/files/centos-stream-9.json: -------------------------------------------------------------------------------- 1 | { 2 | "aarch64": [ 3 | { 4 | "name": "baseos", 5 | "baseurl": "https://composes.stream.centos.org/production/latest-CentOS-Stream/compose/BaseOS/aarch64/os/", 6 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\nmQINBFzMWxkBEADHrskpBgN9OphmhRkc7P/YrsAGSvvl7kfu+e9KAaU6f5MeAVyn\nrIoM43syyGkgFyWgjZM8/rur7EMPY2yt+2q/1ZfLVCRn9856JqTIq0XRpDUe4nKQ\n8BlA7wDVZoSDxUZkSuTIyExbDf0cpw89Tcf62Mxmi8jh74vRlPy1PgjWL5494b3X\n5fxDidH4bqPZyxTBqPrUFuo+EfUVEqiGF94Ppq6ZUvrBGOVo1V1+Ifm9CGEK597c\naevcGc1RFlgxIgN84UpuDjPR9/zSndwJ7XsXYvZ6HXcKGagRKsfYDWGPkA5cOL/e\nf+yObOnC43yPUvpggQ4KaNJ6+SMTZOKikM8yciyBwLqwrjo8FlJgkv8Vfag/2UR7\nJINbyqHHoLUhQ2m6HXSwK4YjtwidF9EUkaBZWrrskYR3IRZLXlWqeOi/+ezYOW0m\nvufrkcvsh+TKlVVnuwmEPjJ8mwUSpsLdfPJo1DHsd8FS03SCKPaXFdD7ePfEjiYk\nnHpQaKE01aWVSLUiygn7F7rYemGqV9Vt7tBw5pz0vqSC72a5E3zFzIIuHx6aANry\nGat3aqU3qtBXOrA/dPkX9cWE+UR5wo/A2UdKJZLlGhM2WRJ3ltmGT48V9CeS6N9Y\nm4CKdzvg7EWjlTlFrd/8WJ2KoqOE9leDPeXRPncubJfJ6LLIHyG09h9kKQARAQAB\ntDpDZW50T1MgKENlbnRPUyBPZmZpY2lhbCBTaWduaW5nIEtleSkgPHNlY3VyaXR5\nQGNlbnRvcy5vcmc+iQI3BBMBAgAhBQJczFsZAhsDBgsJCAcDAgYVCAIJCgsDFgIB\nAh4BAheAAAoJEAW1VbOEg8ZdjOsP/2ygSxH9jqffOU9SKyJDlraL2gIutqZ3B8pl\nGy/Qnb9QD1EJVb4ZxOEhcY2W9VJfIpnf3yBuAto7zvKe/G1nxH4Bt6WTJQCkUjcs\nN3qPWsx1VslsAEz7bXGiHym6Ay4xF28bQ9XYIokIQXd0T2rD3/lNGxNtORZ2bKjD\nvOzYzvh2idUIY1DgGWJ11gtHFIA9CvHcW+SMPEhkcKZJAO51ayFBqTSSpiorVwTq\na0cB+cgmCQOI4/MY+kIvzoexfG7xhkUqe0wxmph9RQQxlTbNQDCdaxSgwbF2T+gw\nbyaDvkS4xtR6Soj7BKjKAmcnf5fn4C5Or0KLUqMzBtDMbfQQihn62iZJN6ZZ/4dg\nq4HTqyVpyuzMXsFpJ9L/FqH2DJ4exGGpBv00ba/Zauy7GsqOc5PnNBsYaHCply0X\n407DRx51t9YwYI/ttValuehq9+gRJpOTTKp6AjZn/a5Yt3h6jDgpNfM/EyLFIY9z\nV6CXqQQ/8JRvaik/JsGCf+eeLZOw4koIjZGEAg04iuyNTjhx0e/QHEVcYAqNLhXG\nrCTTbCn3NSUO9qxEXC+K/1m1kaXoCGA0UWlVGZ1JSifbbMx0yxq/brpEZPUYm+32\no8XfbocBWljFUJ+6aljTvZ3LQLKTSPW7TFO+GXycAOmCGhlXh2tlc6iTc41PACqy\nyy+mHmSv\n=kkH7\n-----END PGP PUBLIC KEY BLOCK-----\n", 7 | "check_gpg": true 8 | }, 9 | { 10 | "name": "appstream", 11 | "baseurl": "https://composes.stream.centos.org/production/latest-CentOS-Stream/compose/AppStream/aarch64/os/", 12 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\nmQINBFzMWxkBEADHrskpBgN9OphmhRkc7P/YrsAGSvvl7kfu+e9KAaU6f5MeAVyn\nrIoM43syyGkgFyWgjZM8/rur7EMPY2yt+2q/1ZfLVCRn9856JqTIq0XRpDUe4nKQ\n8BlA7wDVZoSDxUZkSuTIyExbDf0cpw89Tcf62Mxmi8jh74vRlPy1PgjWL5494b3X\n5fxDidH4bqPZyxTBqPrUFuo+EfUVEqiGF94Ppq6ZUvrBGOVo1V1+Ifm9CGEK597c\naevcGc1RFlgxIgN84UpuDjPR9/zSndwJ7XsXYvZ6HXcKGagRKsfYDWGPkA5cOL/e\nf+yObOnC43yPUvpggQ4KaNJ6+SMTZOKikM8yciyBwLqwrjo8FlJgkv8Vfag/2UR7\nJINbyqHHoLUhQ2m6HXSwK4YjtwidF9EUkaBZWrrskYR3IRZLXlWqeOi/+ezYOW0m\nvufrkcvsh+TKlVVnuwmEPjJ8mwUSpsLdfPJo1DHsd8FS03SCKPaXFdD7ePfEjiYk\nnHpQaKE01aWVSLUiygn7F7rYemGqV9Vt7tBw5pz0vqSC72a5E3zFzIIuHx6aANry\nGat3aqU3qtBXOrA/dPkX9cWE+UR5wo/A2UdKJZLlGhM2WRJ3ltmGT48V9CeS6N9Y\nm4CKdzvg7EWjlTlFrd/8WJ2KoqOE9leDPeXRPncubJfJ6LLIHyG09h9kKQARAQAB\ntDpDZW50T1MgKENlbnRPUyBPZmZpY2lhbCBTaWduaW5nIEtleSkgPHNlY3VyaXR5\nQGNlbnRvcy5vcmc+iQI3BBMBAgAhBQJczFsZAhsDBgsJCAcDAgYVCAIJCgsDFgIB\nAh4BAheAAAoJEAW1VbOEg8ZdjOsP/2ygSxH9jqffOU9SKyJDlraL2gIutqZ3B8pl\nGy/Qnb9QD1EJVb4ZxOEhcY2W9VJfIpnf3yBuAto7zvKe/G1nxH4Bt6WTJQCkUjcs\nN3qPWsx1VslsAEz7bXGiHym6Ay4xF28bQ9XYIokIQXd0T2rD3/lNGxNtORZ2bKjD\nvOzYzvh2idUIY1DgGWJ11gtHFIA9CvHcW+SMPEhkcKZJAO51ayFBqTSSpiorVwTq\na0cB+cgmCQOI4/MY+kIvzoexfG7xhkUqe0wxmph9RQQxlTbNQDCdaxSgwbF2T+gw\nbyaDvkS4xtR6Soj7BKjKAmcnf5fn4C5Or0KLUqMzBtDMbfQQihn62iZJN6ZZ/4dg\nq4HTqyVpyuzMXsFpJ9L/FqH2DJ4exGGpBv00ba/Zauy7GsqOc5PnNBsYaHCply0X\n407DRx51t9YwYI/ttValuehq9+gRJpOTTKp6AjZn/a5Yt3h6jDgpNfM/EyLFIY9z\nV6CXqQQ/8JRvaik/JsGCf+eeLZOw4koIjZGEAg04iuyNTjhx0e/QHEVcYAqNLhXG\nrCTTbCn3NSUO9qxEXC+K/1m1kaXoCGA0UWlVGZ1JSifbbMx0yxq/brpEZPUYm+32\no8XfbocBWljFUJ+6aljTvZ3LQLKTSPW7TFO+GXycAOmCGhlXh2tlc6iTc41PACqy\nyy+mHmSv\n=kkH7\n-----END PGP PUBLIC KEY BLOCK-----\n", 13 | "check_gpg": true 14 | } 15 | ], 16 | "x86_64": [ 17 | { 18 | "name": "baseos", 19 | "baseurl": "https://composes.stream.centos.org/production/latest-CentOS-Stream/compose/BaseOS/x86_64/os/", 20 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\nmQINBFzMWxkBEADHrskpBgN9OphmhRkc7P/YrsAGSvvl7kfu+e9KAaU6f5MeAVyn\nrIoM43syyGkgFyWgjZM8/rur7EMPY2yt+2q/1ZfLVCRn9856JqTIq0XRpDUe4nKQ\n8BlA7wDVZoSDxUZkSuTIyExbDf0cpw89Tcf62Mxmi8jh74vRlPy1PgjWL5494b3X\n5fxDidH4bqPZyxTBqPrUFuo+EfUVEqiGF94Ppq6ZUvrBGOVo1V1+Ifm9CGEK597c\naevcGc1RFlgxIgN84UpuDjPR9/zSndwJ7XsXYvZ6HXcKGagRKsfYDWGPkA5cOL/e\nf+yObOnC43yPUvpggQ4KaNJ6+SMTZOKikM8yciyBwLqwrjo8FlJgkv8Vfag/2UR7\nJINbyqHHoLUhQ2m6HXSwK4YjtwidF9EUkaBZWrrskYR3IRZLXlWqeOi/+ezYOW0m\nvufrkcvsh+TKlVVnuwmEPjJ8mwUSpsLdfPJo1DHsd8FS03SCKPaXFdD7ePfEjiYk\nnHpQaKE01aWVSLUiygn7F7rYemGqV9Vt7tBw5pz0vqSC72a5E3zFzIIuHx6aANry\nGat3aqU3qtBXOrA/dPkX9cWE+UR5wo/A2UdKJZLlGhM2WRJ3ltmGT48V9CeS6N9Y\nm4CKdzvg7EWjlTlFrd/8WJ2KoqOE9leDPeXRPncubJfJ6LLIHyG09h9kKQARAQAB\ntDpDZW50T1MgKENlbnRPUyBPZmZpY2lhbCBTaWduaW5nIEtleSkgPHNlY3VyaXR5\nQGNlbnRvcy5vcmc+iQI3BBMBAgAhBQJczFsZAhsDBgsJCAcDAgYVCAIJCgsDFgIB\nAh4BAheAAAoJEAW1VbOEg8ZdjOsP/2ygSxH9jqffOU9SKyJDlraL2gIutqZ3B8pl\nGy/Qnb9QD1EJVb4ZxOEhcY2W9VJfIpnf3yBuAto7zvKe/G1nxH4Bt6WTJQCkUjcs\nN3qPWsx1VslsAEz7bXGiHym6Ay4xF28bQ9XYIokIQXd0T2rD3/lNGxNtORZ2bKjD\nvOzYzvh2idUIY1DgGWJ11gtHFIA9CvHcW+SMPEhkcKZJAO51ayFBqTSSpiorVwTq\na0cB+cgmCQOI4/MY+kIvzoexfG7xhkUqe0wxmph9RQQxlTbNQDCdaxSgwbF2T+gw\nbyaDvkS4xtR6Soj7BKjKAmcnf5fn4C5Or0KLUqMzBtDMbfQQihn62iZJN6ZZ/4dg\nq4HTqyVpyuzMXsFpJ9L/FqH2DJ4exGGpBv00ba/Zauy7GsqOc5PnNBsYaHCply0X\n407DRx51t9YwYI/ttValuehq9+gRJpOTTKp6AjZn/a5Yt3h6jDgpNfM/EyLFIY9z\nV6CXqQQ/8JRvaik/JsGCf+eeLZOw4koIjZGEAg04iuyNTjhx0e/QHEVcYAqNLhXG\nrCTTbCn3NSUO9qxEXC+K/1m1kaXoCGA0UWlVGZ1JSifbbMx0yxq/brpEZPUYm+32\no8XfbocBWljFUJ+6aljTvZ3LQLKTSPW7TFO+GXycAOmCGhlXh2tlc6iTc41PACqy\nyy+mHmSv\n=kkH7\n-----END PGP PUBLIC KEY BLOCK-----\n", 21 | "check_gpg": true 22 | }, 23 | { 24 | "name": "appstream", 25 | "baseurl": "https://composes.stream.centos.org/production/latest-CentOS-Stream/compose/AppStream/x86_64/os/", 26 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\nmQINBFzMWxkBEADHrskpBgN9OphmhRkc7P/YrsAGSvvl7kfu+e9KAaU6f5MeAVyn\nrIoM43syyGkgFyWgjZM8/rur7EMPY2yt+2q/1ZfLVCRn9856JqTIq0XRpDUe4nKQ\n8BlA7wDVZoSDxUZkSuTIyExbDf0cpw89Tcf62Mxmi8jh74vRlPy1PgjWL5494b3X\n5fxDidH4bqPZyxTBqPrUFuo+EfUVEqiGF94Ppq6ZUvrBGOVo1V1+Ifm9CGEK597c\naevcGc1RFlgxIgN84UpuDjPR9/zSndwJ7XsXYvZ6HXcKGagRKsfYDWGPkA5cOL/e\nf+yObOnC43yPUvpggQ4KaNJ6+SMTZOKikM8yciyBwLqwrjo8FlJgkv8Vfag/2UR7\nJINbyqHHoLUhQ2m6HXSwK4YjtwidF9EUkaBZWrrskYR3IRZLXlWqeOi/+ezYOW0m\nvufrkcvsh+TKlVVnuwmEPjJ8mwUSpsLdfPJo1DHsd8FS03SCKPaXFdD7ePfEjiYk\nnHpQaKE01aWVSLUiygn7F7rYemGqV9Vt7tBw5pz0vqSC72a5E3zFzIIuHx6aANry\nGat3aqU3qtBXOrA/dPkX9cWE+UR5wo/A2UdKJZLlGhM2WRJ3ltmGT48V9CeS6N9Y\nm4CKdzvg7EWjlTlFrd/8WJ2KoqOE9leDPeXRPncubJfJ6LLIHyG09h9kKQARAQAB\ntDpDZW50T1MgKENlbnRPUyBPZmZpY2lhbCBTaWduaW5nIEtleSkgPHNlY3VyaXR5\nQGNlbnRvcy5vcmc+iQI3BBMBAgAhBQJczFsZAhsDBgsJCAcDAgYVCAIJCgsDFgIB\nAh4BAheAAAoJEAW1VbOEg8ZdjOsP/2ygSxH9jqffOU9SKyJDlraL2gIutqZ3B8pl\nGy/Qnb9QD1EJVb4ZxOEhcY2W9VJfIpnf3yBuAto7zvKe/G1nxH4Bt6WTJQCkUjcs\nN3qPWsx1VslsAEz7bXGiHym6Ay4xF28bQ9XYIokIQXd0T2rD3/lNGxNtORZ2bKjD\nvOzYzvh2idUIY1DgGWJ11gtHFIA9CvHcW+SMPEhkcKZJAO51ayFBqTSSpiorVwTq\na0cB+cgmCQOI4/MY+kIvzoexfG7xhkUqe0wxmph9RQQxlTbNQDCdaxSgwbF2T+gw\nbyaDvkS4xtR6Soj7BKjKAmcnf5fn4C5Or0KLUqMzBtDMbfQQihn62iZJN6ZZ/4dg\nq4HTqyVpyuzMXsFpJ9L/FqH2DJ4exGGpBv00ba/Zauy7GsqOc5PnNBsYaHCply0X\n407DRx51t9YwYI/ttValuehq9+gRJpOTTKp6AjZn/a5Yt3h6jDgpNfM/EyLFIY9z\nV6CXqQQ/8JRvaik/JsGCf+eeLZOw4koIjZGEAg04iuyNTjhx0e/QHEVcYAqNLhXG\nrCTTbCn3NSUO9qxEXC+K/1m1kaXoCGA0UWlVGZ1JSifbbMx0yxq/brpEZPUYm+32\no8XfbocBWljFUJ+6aljTvZ3LQLKTSPW7TFO+GXycAOmCGhlXh2tlc6iTc41PACqy\nyy+mHmSv\n=kkH7\n-----END PGP PUBLIC KEY BLOCK-----\n", 27 | "check_gpg": true 28 | }, 29 | { 30 | "name": "rt", 31 | "baseurl": "https://composes.stream.centos.org/production/latest-CentOS-Stream/compose/RT/x86_64/os/", 32 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\nmQINBFzMWxkBEADHrskpBgN9OphmhRkc7P/YrsAGSvvl7kfu+e9KAaU6f5MeAVyn\nrIoM43syyGkgFyWgjZM8/rur7EMPY2yt+2q/1ZfLVCRn9856JqTIq0XRpDUe4nKQ\n8BlA7wDVZoSDxUZkSuTIyExbDf0cpw89Tcf62Mxmi8jh74vRlPy1PgjWL5494b3X\n5fxDidH4bqPZyxTBqPrUFuo+EfUVEqiGF94Ppq6ZUvrBGOVo1V1+Ifm9CGEK597c\naevcGc1RFlgxIgN84UpuDjPR9/zSndwJ7XsXYvZ6HXcKGagRKsfYDWGPkA5cOL/e\nf+yObOnC43yPUvpggQ4KaNJ6+SMTZOKikM8yciyBwLqwrjo8FlJgkv8Vfag/2UR7\nJINbyqHHoLUhQ2m6HXSwK4YjtwidF9EUkaBZWrrskYR3IRZLXlWqeOi/+ezYOW0m\nvufrkcvsh+TKlVVnuwmEPjJ8mwUSpsLdfPJo1DHsd8FS03SCKPaXFdD7ePfEjiYk\nnHpQaKE01aWVSLUiygn7F7rYemGqV9Vt7tBw5pz0vqSC72a5E3zFzIIuHx6aANry\nGat3aqU3qtBXOrA/dPkX9cWE+UR5wo/A2UdKJZLlGhM2WRJ3ltmGT48V9CeS6N9Y\nm4CKdzvg7EWjlTlFrd/8WJ2KoqOE9leDPeXRPncubJfJ6LLIHyG09h9kKQARAQAB\ntDpDZW50T1MgKENlbnRPUyBPZmZpY2lhbCBTaWduaW5nIEtleSkgPHNlY3VyaXR5\nQGNlbnRvcy5vcmc+iQI3BBMBAgAhBQJczFsZAhsDBgsJCAcDAgYVCAIJCgsDFgIB\nAh4BAheAAAoJEAW1VbOEg8ZdjOsP/2ygSxH9jqffOU9SKyJDlraL2gIutqZ3B8pl\nGy/Qnb9QD1EJVb4ZxOEhcY2W9VJfIpnf3yBuAto7zvKe/G1nxH4Bt6WTJQCkUjcs\nN3qPWsx1VslsAEz7bXGiHym6Ay4xF28bQ9XYIokIQXd0T2rD3/lNGxNtORZ2bKjD\nvOzYzvh2idUIY1DgGWJ11gtHFIA9CvHcW+SMPEhkcKZJAO51ayFBqTSSpiorVwTq\na0cB+cgmCQOI4/MY+kIvzoexfG7xhkUqe0wxmph9RQQxlTbNQDCdaxSgwbF2T+gw\nbyaDvkS4xtR6Soj7BKjKAmcnf5fn4C5Or0KLUqMzBtDMbfQQihn62iZJN6ZZ/4dg\nq4HTqyVpyuzMXsFpJ9L/FqH2DJ4exGGpBv00ba/Zauy7GsqOc5PnNBsYaHCply0X\n407DRx51t9YwYI/ttValuehq9+gRJpOTTKp6AjZn/a5Yt3h6jDgpNfM/EyLFIY9z\nV6CXqQQ/8JRvaik/JsGCf+eeLZOw4koIjZGEAg04iuyNTjhx0e/QHEVcYAqNLhXG\nrCTTbCn3NSUO9qxEXC+K/1m1kaXoCGA0UWlVGZ1JSifbbMx0yxq/brpEZPUYm+32\no8XfbocBWljFUJ+6aljTvZ3LQLKTSPW7TFO+GXycAOmCGhlXh2tlc6iTc41PACqy\nyy+mHmSv\n=kkH7\n-----END PGP PUBLIC KEY BLOCK-----\n", 33 | "check_gpg": true 34 | }, 35 | { 36 | "name": "packages", 37 | "baseurl": "http://192.168.100.1/packages/" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/comment-ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI trigger by comment in pull request 3 | 4 | on: 5 | issue_comment: 6 | types: [created] 7 | 8 | jobs: 9 | check-pull-request: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.event.issue.pull_request && 12 | (endsWith(github.event.comment.body, '/test')) }} 13 | steps: 14 | - name: Query author repository permissions 15 | uses: octokit/request-action@v2.x 16 | id: user_permission 17 | with: 18 | route: GET /repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - name: Check if user does have correct permissions 23 | if: contains('admin write', fromJson(steps.user_permission.outputs.data).permission) 24 | id: check_user_perm 25 | run: | 26 | echo "User '${{ github.event.comment.user.login }}' has permission '${{ fromJson(steps.user_permission.outputs.data).permission }}' allowed values: 'admin', 'write'" 27 | echo "allowed_user=true" >> $GITHUB_OUTPUT 28 | 29 | - name: Get information for pull request 30 | uses: octokit/request-action@v2.x 31 | id: pr-api 32 | with: 33 | route: GET /repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | outputs: 38 | allowed_user: ${{ steps.check_user_perm.outputs.allowed_user }} 39 | sha: ${{ fromJson(steps.pr-api.outputs.data).head.sha }} 40 | ref: ${{ fromJson(steps.pr-api.outputs.data).head.ref }} 41 | repo_url: ${{ fromJson(steps.pr-api.outputs.data).head.repo.html_url }} 42 | 43 | Fedora-41-bootc: 44 | needs: check-pull-request 45 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 46 | continue-on-error: true 47 | runs-on: ubuntu-latest 48 | 49 | steps: 50 | - name: Run the tests 51 | uses: sclorg/testing-farm-as-github-action@v3.1.2 52 | with: 53 | compose: Fedora-41 54 | api_key: ${{ secrets.TF_API_KEY }} 55 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 56 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 57 | update_pull_request_status: true 58 | pull_request_status_name: "Fedora-41-bootc" 59 | tmt_context: "arch=x86_64;distro=fedora-41" 60 | tmt_plan_regex: bootc 61 | tf_scope: private 62 | variables: "ARCH=x86_64" 63 | timeout: 90 64 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 65 | 66 | Centos-stream-9-bootc: 67 | needs: check-pull-request 68 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 69 | continue-on-error: true 70 | runs-on: ubuntu-latest 71 | 72 | steps: 73 | - name: Run the tests 74 | uses: sclorg/testing-farm-as-github-action@v3.1.2 75 | with: 76 | compose: CentOS-Stream-9 77 | api_key: ${{ secrets.TF_API_KEY }} 78 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 79 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 80 | update_pull_request_status: true 81 | pull_request_status_name: "Centos-stream-9-bootc" 82 | tmt_context: "arch=x86_64;distro=cs-9" 83 | tmt_plan_regex: bootc 84 | tf_scope: private 85 | variables: "ARCH=x86_64" 86 | timeout: 90 87 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 88 | 89 | Centos-stream-9-rpm-ostree: 90 | needs: check-pull-request 91 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 92 | continue-on-error: true 93 | runs-on: ubuntu-latest 94 | 95 | steps: 96 | - name: Run the tests 97 | uses: sclorg/testing-farm-as-github-action@v3.1.2 98 | with: 99 | compose: CentOS-Stream-9 100 | api_key: ${{ secrets.TF_API_KEY }} 101 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 102 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 103 | update_pull_request_status: true 104 | pull_request_status_name: "Centos-stream-9-rpm-ostree" 105 | tmt_context: "arch=x86_64;distro=cs-9" 106 | tmt_plan_regex: rpm 107 | tf_scope: private 108 | variables: "ARCH=x86_64" 109 | timeout: 90 110 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 111 | 112 | Centos-stream-10-bootc: 113 | needs: check-pull-request 114 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 115 | continue-on-error: true 116 | runs-on: ubuntu-latest 117 | 118 | steps: 119 | - name: Run the tests 120 | uses: sclorg/testing-farm-as-github-action@v3.1.2 121 | with: 122 | compose: CentOS-Stream-10 123 | api_key: ${{ secrets.TF_API_KEY }} 124 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 125 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 126 | update_pull_request_status: true 127 | pull_request_status_name: "Centos-stream-10-bootc" 128 | tmt_context: "arch=x86_64;distro=cs-10" 129 | tmt_plan_regex: bootc 130 | tf_scope: private 131 | variables: "ARCH=x86_64" 132 | timeout: 90 133 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 134 | 135 | RHEL-96-bootc: 136 | needs: check-pull-request 137 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 138 | continue-on-error: true 139 | runs-on: ubuntu-latest 140 | 141 | steps: 142 | - name: Run the tests 143 | uses: sclorg/testing-farm-as-github-action@v3.1.2 144 | with: 145 | compose: RHEL-9.6.0-Nightly 146 | api_key: ${{ secrets.TF_API_KEY }} 147 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 148 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 149 | update_pull_request_status: true 150 | pull_request_status_name: "RHEL-96-bootc" 151 | tmt_context: "arch=x86_64;distro=rhel-9-6" 152 | tmt_plan_regex: bootc 153 | tf_scope: private 154 | variables: "ARCH=x86_64" 155 | timeout: 90 156 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 157 | 158 | RHEL-96-rpm-ostree: 159 | needs: check-pull-request 160 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 161 | continue-on-error: true 162 | runs-on: ubuntu-latest 163 | 164 | steps: 165 | - name: Run the tests 166 | uses: sclorg/testing-farm-as-github-action@v3.1.2 167 | with: 168 | compose: RHEL-9.6.0-Nightly 169 | api_key: ${{ secrets.TF_API_KEY }} 170 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 171 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 172 | update_pull_request_status: true 173 | pull_request_status_name: "RHEL-96-rpm-ostree" 174 | tmt_context: "arch=x86_64;distro=rhel-9-6" 175 | tmt_plan_regex: rpm 176 | tf_scope: private 177 | variables: "ARCH=x86_64" 178 | timeout: 90 179 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 180 | 181 | RHEL-10-bootc: 182 | needs: check-pull-request 183 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 184 | continue-on-error: true 185 | runs-on: ubuntu-latest 186 | 187 | steps: 188 | - name: Run the tests 189 | uses: sclorg/testing-farm-as-github-action@v3.1.2 190 | with: 191 | compose: RHEL-10.0-Nightly 192 | api_key: ${{ secrets.TF_API_KEY }} 193 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 194 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 195 | update_pull_request_status: true 196 | pull_request_status_name: "RHEL-10-bootc" 197 | tmt_context: "arch=x86_64;distro=rhel-10-0" 198 | tmt_plan_regex: bootc 199 | tf_scope: private 200 | variables: "ARCH=x86_64" 201 | timeout: 90 202 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 203 | -------------------------------------------------------------------------------- /.github/workflows/greenboot-ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI Test 3 | 4 | on: 5 | pull_request_target: 6 | types: [opened, synchronize, reopened] 7 | 8 | jobs: 9 | check-pull-request: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Query author repository permissions 13 | uses: octokit/request-action@v2.x 14 | id: user_permission 15 | with: 16 | route: GET /repos/${{ github.repository }}/collaborators/${{ github.event.sender.login }}/permission 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | - name: Check if user does have correct permissions 21 | if: contains('admin write', fromJson(steps.user_permission.outputs.data).permission) 22 | id: check_user_perm 23 | run: | 24 | echo "User '${{ github.event.sender.login }}' has permission '${{ fromJson(steps.user_permission.outputs.data).permission }}' allowed values: 'admin', 'write'" 25 | echo "allowed_user=true" >> $GITHUB_OUTPUT 26 | 27 | - name: Get information for pull request 28 | uses: octokit/request-action@v2.x 29 | id: pr-api 30 | with: 31 | route: GET /repos/${{ github.repository }}/pulls/${{ github.event.number }} 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | 35 | outputs: 36 | allowed_user: ${{ steps.check_user_perm.outputs.allowed_user }} 37 | sha: ${{ fromJson(steps.pr-api.outputs.data).head.sha }} 38 | ref: ${{ fromJson(steps.pr-api.outputs.data).head.ref }} 39 | repo_url: ${{ fromJson(steps.pr-api.outputs.data).head.repo.html_url }} 40 | 41 | Fedora-41-bootc: 42 | needs: check-pull-request 43 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 44 | continue-on-error: true 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - name: Run the tests 49 | uses: sclorg/testing-farm-as-github-action@v3.1.2 50 | with: 51 | compose: Fedora-41 52 | api_key: ${{ secrets.TF_API_KEY }} 53 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 54 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 55 | # update_pull_request_status: true 56 | # pull_request_status_name: "Fedora-41-bootc" 57 | tmt_context: "arch=x86_64;distro=fedora-41" 58 | tmt_plan_regex: bootc 59 | tf_scope: private 60 | variables: "ARCH=x86_64" 61 | timeout: 90 62 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 63 | 64 | # Fedora-rawhide-bootc: 65 | # needs: check-pull-request 66 | # if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 67 | # continue-on-error: true 68 | # runs-on: ubuntu-latest 69 | 70 | # steps: 71 | # - name: Run the tests 72 | # uses: sclorg/testing-farm-as-github-action@v3.1.2 73 | # with: 74 | # compose: Fedora-Rawhide 75 | # api_key: ${{ secrets.TF_API_KEY }} 76 | # git_url: ${{ needs.check-pull-request.outputs.repo_url }} 77 | # git_ref: ${{ needs.check-pull-request.outputs.ref }} 78 | # tmt_context: "arch=x86_64;distro=fedora-rawhide" 79 | # tmt_plan_regex: bootc 80 | # tf_scope: private 81 | # secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }}" 82 | # variables: "ARCH=x86_64" 83 | # timeout: 90 84 | 85 | Centos-stream-9-bootc: 86 | needs: check-pull-request 87 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 88 | continue-on-error: true 89 | runs-on: ubuntu-latest 90 | 91 | steps: 92 | - name: Run the tests 93 | uses: sclorg/testing-farm-as-github-action@v3.1.2 94 | with: 95 | compose: CentOS-Stream-9 96 | api_key: ${{ secrets.TF_API_KEY }} 97 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 98 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 99 | tmt_context: "arch=x86_64;distro=cs-9" 100 | tmt_plan_regex: bootc 101 | tf_scope: private 102 | variables: "ARCH=x86_64" 103 | timeout: 90 104 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 105 | 106 | Centos-stream-9-rpm-ostree: 107 | needs: check-pull-request 108 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 109 | continue-on-error: true 110 | runs-on: ubuntu-latest 111 | 112 | steps: 113 | - name: Run the tests 114 | uses: sclorg/testing-farm-as-github-action@v3.1.2 115 | with: 116 | compose: CentOS-Stream-9 117 | api_key: ${{ secrets.TF_API_KEY }} 118 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 119 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 120 | tmt_context: "arch=x86_64;distro=cs-9" 121 | tmt_plan_regex: rpm 122 | tf_scope: private 123 | variables: "ARCH=x86_64" 124 | timeout: 90 125 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 126 | 127 | Centos-stream-10-bootc: 128 | needs: check-pull-request 129 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 130 | continue-on-error: true 131 | runs-on: ubuntu-latest 132 | 133 | steps: 134 | - name: Run the tests 135 | uses: sclorg/testing-farm-as-github-action@v3.1.2 136 | with: 137 | compose: CentOS-Stream-10 138 | api_key: ${{ secrets.TF_API_KEY }} 139 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 140 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 141 | tmt_context: "arch=x86_64;distro=cs-10" 142 | tmt_plan_regex: bootc 143 | tf_scope: private 144 | variables: "ARCH=x86_64" 145 | timeout: 90 146 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 147 | 148 | RHEL-96-bootc: 149 | needs: check-pull-request 150 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 151 | continue-on-error: true 152 | runs-on: ubuntu-latest 153 | 154 | steps: 155 | - name: Run the tests 156 | uses: sclorg/testing-farm-as-github-action@v3.1.2 157 | with: 158 | compose: RHEL-9.6.0-Nightly 159 | api_key: ${{ secrets.TF_API_KEY }} 160 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 161 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 162 | tmt_context: "arch=x86_64;distro=rhel-9-6" 163 | tmt_plan_regex: bootc 164 | tf_scope: private 165 | variables: "ARCH=x86_64" 166 | timeout: 90 167 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 168 | 169 | RHEL-96-rpm-ostree: 170 | needs: check-pull-request 171 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 172 | continue-on-error: true 173 | runs-on: ubuntu-latest 174 | 175 | steps: 176 | - name: Run the tests 177 | uses: sclorg/testing-farm-as-github-action@v3.1.2 178 | with: 179 | compose: RHEL-9.6.0-Nightly 180 | api_key: ${{ secrets.TF_API_KEY }} 181 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 182 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 183 | tmt_context: "arch=x86_64;distro=rhel-9-6" 184 | tmt_plan_regex: rpm 185 | tf_scope: private 186 | variables: "ARCH=x86_64" 187 | timeout: 90 188 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 189 | 190 | RHEL-10-bootc: 191 | needs: check-pull-request 192 | if: ${{ needs.check-pull-request.outputs.allowed_user == 'true' }} 193 | continue-on-error: true 194 | runs-on: ubuntu-latest 195 | 196 | steps: 197 | - name: Run the tests 198 | uses: sclorg/testing-farm-as-github-action@v3.1.2 199 | with: 200 | compose: RHEL-10.0-Nightly 201 | api_key: ${{ secrets.TF_API_KEY }} 202 | git_url: ${{ needs.check-pull-request.outputs.repo_url }} 203 | git_ref: ${{ needs.check-pull-request.outputs.ref }} 204 | tmt_context: "arch=x86_64;distro=rhel-10-0" 205 | tmt_plan_regex: bootc 206 | tf_scope: private 207 | variables: "ARCH=x86_64" 208 | timeout: 90 209 | secrets: "QUAY_USERNAME=${{ secrets.QUAY_USERNAME }};QUAY_PASSWORD=${{ secrets.QUAY_PASSWORD }};STAGE_REDHAT_IO_USERNAME=${{ secrets.STAGE_REDHAT_IO_USERNAME }};STAGE_REDHAT_IO_TOKEN=${{ secrets.STAGE_REDHAT_IO_TOKEN }};DOWNLOAD_NODE=${{ secrets.DOWNLOAD_NODE }}" 210 | -------------------------------------------------------------------------------- /.github/workflows/greenboot-rs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: greenboot rs integration test 3 | 4 | on: 5 | issue_comment: 6 | types: 7 | - created 8 | 9 | jobs: 10 | pr-info: 11 | if: ${{ github.event.issue.pull_request && 12 | (startsWith(github.event.comment.body, '/greenboot-rs-test-all') || 13 | startsWith(github.event.comment.body, '/greenboot-rs-test-39')) }} 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Query author repository permissions 17 | uses: octokit/request-action@v2.x 18 | id: user_permission 19 | with: 20 | route: GET /repos/${{ github.repository }}/collaborators/${{ github.event.sender.login }}/permission 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | # restrict running of tests to users with admin or write permission for the repository 25 | # see https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-repository-permissions-for-a-user 26 | - name: Check if user does have correct permissions 27 | if: contains('admin write', fromJson(steps.user_permission.outputs.data).permission) 28 | id: check_user_perm 29 | run: | 30 | echo "User '${{ github.event.sender.login }}' has permission '${{ fromJson(steps.user_permission.outputs.data).permission }}' allowed values: 'admin', 'write'" 31 | echo "allowed_user=true" >> $GITHUB_OUTPUT 32 | 33 | - name: Get information for pull request 34 | uses: octokit/request-action@v2.x 35 | id: pr-api 36 | with: 37 | route: GET /repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | outputs: 42 | allowed_user: ${{ steps.check_user_perm.outputs.allowed_user }} 43 | sha: ${{ fromJson(steps.pr-api.outputs.data).head.sha }} 44 | base_ref: ${{ fromJson(steps.pr-api.outputs.data).base.ref }} 45 | 46 | comment-info: 47 | needs: pr-info 48 | if: ${{ needs.pr-info.outputs.allowed_user == 'true' }} 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: PR comment analysis 52 | id: comment-analysis 53 | run: | 54 | comment_content="${{ github.event.comment.body }}" 55 | comment_array=($comment_content) 56 | comment_arg_len=${#comment_array[@]} 57 | 58 | echo ${comment_array[@]} 59 | echo $comment_arg_len 60 | 61 | # Default to osbuild and osbuild-composer main branch 62 | IMAGES_REPO="osbuild/images" 63 | IMAGES_BRANCH="main" 64 | OSBUILD_COMPOSER_REPO="osbuild/osbuild-composer" 65 | OSBUILD_COMPOSER_BRANCH="main" 66 | 67 | for item in "${comment_array[@]}"; do 68 | if [[ "$item" =~ "/images:" ]]; then 69 | IMAGES_REPO="$(echo $item | cut -d: -f1)" 70 | IMAGES_BRANCH="$(echo $item | cut -d: -f2)" 71 | fi 72 | if [[ "$item" =~ "/osbuild-composer:" ]]; then 73 | OSBUILD_COMPOSER_REPO="$(echo $item | cut -d: -f1)" 74 | OSBUILD_COMPOSER_BRANCH="$(echo $item | cut -d: -f2)" 75 | fi 76 | done 77 | 78 | echo $IMAGES_REPO 79 | echo $IMAGES_BRANCH 80 | echo $OSBUILD_COMPOSER_REPO 81 | echo $OSBUILD_COMPOSER_BRANCH 82 | 83 | echo "images_repo=$IMAGES_REPO" >> $GITHUB_OUTPUT 84 | echo "images_branch=$IMAGES_BRANCH" >> $GITHUB_OUTPUT 85 | echo "osbuild-composer_repo=$OSBUILD_COMPOSER_REPO" >> $GITHUB_OUTPUT 86 | echo "osbuild-composer_branch=$OSBUILD_COMPOSER_BRANCH" >> $GITHUB_OUTPUT 87 | 88 | outputs: 89 | images_repo: ${{ steps.comment-analysis.outputs.images_repo }} 90 | images_branch: ${{ steps.comment-analysis.outputs.images_branch }} 91 | osbuild-composer_repo: ${{ steps.comment-analysis.outputs.osbuild-composer_repo }} 92 | osbuild-composer_branch: ${{ steps.comment-analysis.outputs.osbuild-composer_branch }} 93 | 94 | pre-greenboot-rs-39: 95 | needs: pr-info 96 | if: ${{ needs.pr-info.outputs.base_ref == 'greenboot-rs' && 97 | needs.pr-info.outputs.allowed_user == 'true' && 98 | (startsWith(github.event.comment.body, '/greenboot-rs-test-all') || 99 | startsWith(github.event.comment.body, '/greenboot-rs-test-39')) }} 100 | runs-on: ubuntu-latest 101 | env: 102 | STATUS_NAME: greenboot-rs-39 103 | 104 | steps: 105 | - name: Create in-progress status 106 | uses: octokit/request-action@v2.x 107 | with: 108 | route: 'POST /repos/${{ github.repository }}/statuses/${{ needs.pr-info.outputs.sha }}' 109 | context: ${{ env.STATUS_NAME }} 110 | state: pending 111 | description: 'Deploy runner' 112 | target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | 116 | greenboot-rs-39: 117 | needs: [pr-info, comment-info, pre-greenboot-rs-39] 118 | if: ${{ needs.pr-info.outputs.base_ref == 'greenboot-rs' && 119 | needs.pr-info.outputs.allowed_user == 'true' && 120 | (startsWith(github.event.comment.body, '/greenboot-rs-test-all') || 121 | startsWith(github.event.comment.body, '/greenboot-rs-test-39')) }} 122 | runs-on: [kite, x86_64, gcp, fedora-39, large] 123 | env: 124 | STATUS_NAME: greenboot-rs-39 125 | 126 | steps: 127 | - name: Create in-progress status 128 | uses: octokit/request-action@v2.x 129 | with: 130 | route: 'POST /repos/${{ github.repository }}/statuses/${{ needs.pr-info.outputs.sha }}' 131 | context: ${{ env.STATUS_NAME }} 132 | state: pending 133 | target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' 134 | env: 135 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 136 | 137 | - name: Install required packages 138 | run: sudo dnf install -y make gcc openssl openssl-devel findutils golang git tpm2-tss-devel swtpm swtpm-tools clevis clevis-luks cryptsetup cryptsetup-devel clang-devel cracklib-dicts rust cargo rust-packaging rpmdevtools python3-docutils createrepo_c libassuan-devel krb5-devel gpgme-devel go-rpm-macros 139 | 140 | - name: Clone repository 141 | uses: actions/checkout@v3 142 | with: 143 | ref: ${{ needs.pr-info.outputs.sha }} 144 | fetch-depth: 0 145 | 146 | - name: Build greenboot PRM pakcages 147 | run: make rpm 148 | 149 | - name: Prepare greenboot 150 | run: | 151 | sudo mkdir -p /var/www/html/source 152 | sudo cp ./rpmbuild/RPMS/x86_64/* /var/www/html/source/ 2>/dev/null || : 153 | sudo createrepo_c /var/www/html/source 154 | sudo restorecon -Rv /var/www/html/source 155 | sudo ls -al /var/www/html/source 156 | 157 | # Only run when PR has osbuild-composer dependence 158 | - name: Checkout images code 159 | if: ${{ needs.comment-info.outputs.images_branch != 'main' }} || ${{ needs.comment-info.outputs.osbuild-composer_branch != 'main' }} 160 | uses: actions/checkout@v3 161 | with: 162 | repository: ${{ needs.comment-info.outputs.images_repo }} 163 | ref: ${{ needs.comment-info.outputs.images_branch }} 164 | path: images 165 | 166 | # Only run when PR has osbuild-composer dependence 167 | - name: Checkout osbuild-composer code 168 | if: ${{ needs.comment-info.outputs.osbuild-composer_branch != 'main' }} || ${{ needs.comment-info.outputs.images_branch != 'main' }} 169 | run: git clone -b ${{ needs.comment-info.outputs.osbuild-composer_branch }} https://github.com/${{ needs.comment-info.outputs.osbuild-composer_repo }} 170 | # uses: actions/checkout@v3 171 | # with: 172 | # repository: ${{ needs.comment-info.outputs.osbuild-composer_repo }} 173 | # ref: ${{ needs.comment-info.outputs.osbuild-composer_branch }} 174 | # path: osbuild-composer 175 | 176 | - name: Build osbuild-composer 177 | if: ${{ needs.comment-info.outputs.osbuild-composer_branch != 'main' }} || ${{ needs.comment-info.outputs.images_branch != 'main' }} 178 | run: | 179 | ls -al 180 | pwd 181 | git status 182 | ls -a ../images 183 | git -C ../images status 184 | go clean -modcache 185 | go mod tidy 186 | go mod edit -replace github.com/osbuild/images=../images 187 | GOPROXY=direct GOSUMDB=off ./tools/prepare-source.sh 188 | 189 | git config --global user.name "greenboot bot" 190 | git config --global user.email "greenboot-bot@greenboot.com" 191 | git status 192 | git add -A 193 | git commit -m "new build for greenboot test" 194 | 195 | make rpm 196 | 197 | sudo cp rpmbuild/RPMS/x86_64/* /var/www/html/source/ 198 | sudo ls -al /var/www/html/source/ 199 | sudo createrepo_c /var/www/html/source 200 | sudo restorecon -Rv /var/www/html/source 201 | 202 | sudo tee "/etc/yum.repos.d/source.repo" > /dev/null << EOF 203 | [source] 204 | name = source 205 | baseurl = file:///var/www/html/source/ 206 | enabled = 1 207 | gpgcheck = 0 208 | priority = 5 209 | EOF 210 | 211 | sudo dnf info osbuild osbuild-composer 212 | working-directory: ./osbuild-composer 213 | 214 | - name: Checkout greenboot ci test code 215 | uses: actions/checkout@v3 216 | with: 217 | repository: yih-redhat/greenboot-ci 218 | path: greenboot-ci 219 | 220 | - name: Run greenboot-rs.sh test 221 | run: ./greenboot-rs.sh 222 | working-directory: ./greenboot-ci/tests 223 | timeout-minutes: 100 224 | 225 | - name: Set result status 226 | if: always() 227 | uses: octokit/request-action@v2.x 228 | with: 229 | route: 'POST /repos/${{ github.repository }}/statuses/${{ needs.pr-info.outputs.sha }}' 230 | context: ${{ env.STATUS_NAME }} 231 | state: ${{ job.status }} 232 | target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' 233 | env: 234 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 235 | -------------------------------------------------------------------------------- /greenboot.spec: -------------------------------------------------------------------------------- 1 | %global debug_package %{nil} 2 | 3 | Name: greenboot 4 | Version: 0.15.9 5 | Release: 1%{?dist} 6 | Summary: Generic Health Check Framework for systemd 7 | License: LGPL-2.1-or-later 8 | 9 | %global repo_owner fedora-iot 10 | %global repo_name %{name} 11 | %global repo_tag v%{version} 12 | 13 | URL: https://github.com/%{repo_owner}/%{repo_name} 14 | Source0: https://github.com/%{repo_owner}/%{repo_name}/archive/%{repo_tag}.tar.gz 15 | 16 | %if 0%{?fedora} || 0%{?rhel} >= 10 17 | ExcludeArch: s390x %{ix86} 18 | %else 19 | ExcludeArch: s390x 20 | %endif 21 | BuildRequires: systemd-rpm-macros 22 | %{?systemd_requires} 23 | Requires: systemd >= 240 24 | Requires: grub2-tools-minimal 25 | Requires: rpm-ostree 26 | # PAM is required to programatically read motd messages from /etc/motd.d/* 27 | # This causes issues with RHEL-8 as the fix isn't there an el8 is on pam-1.3.x 28 | Requires: pam >= 1.4.0 29 | # While not strictly necessary to generate the motd, the main use-case of this package is to display it on SSH login 30 | Recommends: openssh 31 | Provides: greenboot-auto-update-fallback 32 | Obsoletes: greenboot-auto-update-fallback <= 0.12.0 33 | Provides: greenboot-grub2 34 | Obsoletes: greenboot-grub2 <= 0.12.0 35 | Provides: greenboot-reboot 36 | Obsoletes: greenboot-reboot <= 0.12.0 37 | Provides: greenboot-status 38 | Obsoletes: greenboot-status <= 0.12.0 39 | Provides: greenboot-rpm-ostree-grub2 40 | Obsoletes: greenboot-rpm-ostree-grub2 <= 0.12.0 41 | 42 | %description 43 | %{summary}. 44 | 45 | %package default-health-checks 46 | Summary: Series of optional and curated health checks 47 | Requires: %{name} = %{version}-%{release} 48 | Requires: util-linux 49 | Requires: jq 50 | Provides: greenboot-update-platforms-check 51 | Obsoletes: greenboot-update-platforms-check <= 0.12.0 52 | 53 | %description default-health-checks 54 | %{summary}. 55 | 56 | %prep 57 | %autosetup 58 | 59 | %build 60 | 61 | %install 62 | mkdir -p %{buildroot}%{_exec_prefix}/lib/motd.d/ 63 | mkdir -p %{buildroot}%{_libexecdir}/%{name} 64 | mkdir -p %{buildroot}%{_sysconfdir}/%{name}/check/required.d 65 | mkdir %{buildroot}%{_sysconfdir}/%{name}/check/wanted.d 66 | mkdir %{buildroot}%{_sysconfdir}/%{name}/green.d 67 | mkdir %{buildroot}%{_sysconfdir}/%{name}/red.d 68 | mkdir -p %{buildroot}%{_prefix}/lib/%{name}/check/required.d 69 | mkdir %{buildroot}%{_prefix}/lib/%{name}/check/wanted.d 70 | mkdir %{buildroot}%{_prefix}/lib/%{name}/green.d 71 | mkdir %{buildroot}%{_prefix}/lib/%{name}/red.d 72 | install -D -t %{buildroot}%{_prefix}/lib/bootupd/grub2-static/configs.d grub2/08_greenboot.cfg 73 | mkdir -p %{buildroot}%{_unitdir} 74 | mkdir -p %{buildroot}%{_unitdir}/greenboot-healthcheck.service.d 75 | mkdir -p %{buildroot}%{_tmpfilesdir} 76 | install -DpZm 0755 usr/libexec/greenboot/* %{buildroot}%{_libexecdir}/%{name} 77 | install -DpZm 0644 usr/lib/motd.d/boot-status %{buildroot}%{_exec_prefix}/lib/motd.d/boot-status 78 | install -DpZm 0644 usr/lib/systemd/system/greenboot-healthcheck.service.d/10-network-online.conf %{buildroot}%{_unitdir}/greenboot-healthcheck.service.d/10-network-online.conf 79 | install -DpZm 0644 usr/lib/systemd/system/*.target %{buildroot}%{_unitdir} 80 | install -DpZm 0644 usr/lib/systemd/system/*.service %{buildroot}%{_unitdir} 81 | install -DpZm 0644 usr/lib/tmpfiles.d/greenboot-status-motd.conf %{buildroot}%{_tmpfilesdir}/greenboot-status-motd.conf 82 | install -DpZm 0755 usr/lib/greenboot/check/required.d/* %{buildroot}%{_prefix}/lib/%{name}/check/required.d 83 | install -DpZm 0755 usr/lib/greenboot/check/wanted.d/* %{buildroot}%{_prefix}/lib/%{name}/check/wanted.d 84 | install -DpZm 0644 etc/greenboot/greenboot.conf %{buildroot}%{_sysconfdir}/%{name}/greenboot.conf 85 | 86 | %post 87 | %systemd_post greenboot-healthcheck.service 88 | %systemd_post greenboot-loading-message.service 89 | %systemd_post greenboot-task-runner.service 90 | %systemd_post redboot-task-runner.service 91 | %systemd_post redboot.target 92 | %systemd_post greenboot-status.service 93 | %systemd_post greenboot-grub2-set-counter.service 94 | %systemd_post greenboot-grub2-set-success.service 95 | %systemd_post greenboot-rpm-ostree-grub2-check-fallback.service 96 | %systemd_post redboot-auto-reboot.service 97 | 98 | %post default-health-checks 99 | %systemd_post greenboot-loading-message.service 100 | 101 | %preun 102 | %systemd_preun greenboot-healthcheck.service 103 | %systemd_preun greenboot-loading-message.service 104 | %systemd_preun greenboot-task-runner.service 105 | %systemd_preun redboot-task-runner.service 106 | %systemd_preun redboot.target 107 | %systemd_preun greenboot-status.service 108 | %systemd_preun greenboot-grub2-set-counter.service 109 | %systemd_preun greenboot-grub2-set-success.service 110 | %systemd_preun greenboot-rpm-ostree-grub2-check-fallback.service 111 | 112 | %preun default-health-checks 113 | %systemd_preun greenboot-loading-message.service 114 | 115 | %postun 116 | %systemd_postun greenboot-healthcheck.service 117 | %systemd_postun greenboot-loading-message.service 118 | %systemd_postun greenboot-task-runner.service 119 | %systemd_postun redboot-task-runner.service 120 | %systemd_postun redboot.target 121 | %systemd_postun greenboot-status.service 122 | %systemd_postun greenboot-grub2-set-counter.service 123 | %systemd_postun greenboot-grub2-set-success.service 124 | %systemd_postun greenboot-rpm-ostree-grub2-check-fallback.service 125 | 126 | %postun default-health-checks 127 | %systemd_postun greenboot-loading-message.service 128 | 129 | %files 130 | %license LICENSE 131 | %doc README.md 132 | %dir %{_sysconfdir}/%{name} 133 | %dir %{_sysconfdir}/%{name}/check 134 | %dir %{_sysconfdir}/%{name}/check/required.d 135 | %dir %{_sysconfdir}/%{name}/check/wanted.d 136 | %dir %{_sysconfdir}/%{name}/green.d 137 | %dir %{_sysconfdir}/%{name}/red.d 138 | %config(noreplace) %{_sysconfdir}/%{name}/greenboot.conf 139 | %dir %{_prefix}/lib/%{name} 140 | %dir %{_prefix}/lib/%{name}/check 141 | %dir %{_prefix}/lib/%{name}/check/required.d 142 | %{_prefix}/lib/%{name}/check/required.d/00_required_scripts_start.sh 143 | %dir %{_prefix}/lib/%{name}/check/wanted.d 144 | %{_prefix}/lib/%{name}/check/wanted.d/00_wanted_scripts_start.sh 145 | %dir %{_prefix}/lib/%{name}/green.d 146 | %dir %{_prefix}/lib/%{name}/red.d 147 | %{_exec_prefix}/lib/motd.d/boot-status 148 | %{_tmpfilesdir}/greenboot-status-motd.conf 149 | %{_prefix}/lib/bootupd/grub2-static/configs.d/*.cfg 150 | %dir %{_libexecdir}/%{name} 151 | %{_libexecdir}/%{name}/%{name} 152 | %{_libexecdir}/%{name}/greenboot-grub2-set-success 153 | %{_libexecdir}/%{name}/greenboot-boot-remount 154 | %{_libexecdir}/%{name}/greenboot-grub2-set-counter 155 | %{_libexecdir}/%{name}/greenboot-loading-message 156 | %{_libexecdir}/%{name}/greenboot-status 157 | %{_libexecdir}/%{name}/greenboot-rpm-ostree-grub2-check-fallback 158 | %{_libexecdir}/%{name}/redboot-auto-reboot 159 | %{_unitdir}/greenboot-grub2-set-counter.service 160 | %{_unitdir}/greenboot-grub2-set-success.service 161 | %{_unitdir}/greenboot-healthcheck.service 162 | %{_unitdir}/greenboot-loading-message.service 163 | %{_unitdir}/greenboot-status.service 164 | %{_unitdir}/greenboot-task-runner.service 165 | %{_unitdir}/greenboot-rpm-ostree-grub2-check-fallback.service 166 | %{_unitdir}/redboot.target 167 | %{_unitdir}/redboot-auto-reboot.service 168 | %{_unitdir}/redboot-task-runner.service 169 | 170 | %files default-health-checks 171 | %{_prefix}/lib/%{name}/check/required.d/01_repository_dns_check.sh 172 | %{_prefix}/lib/%{name}/check/wanted.d/01_update_platforms_check.sh 173 | %{_unitdir}/greenboot-healthcheck.service.d/10-network-online.conf 174 | %{_prefix}/lib/%{name}/check/required.d/02_watchdog.sh 175 | 176 | %changelog 177 | * Tue Mar 25 2025 Sayan Paul - - 0.15.9-1 178 | - Bump to 0.15.9 179 | - Bootupd grub2 static ordering 180 | 181 | * Fri Feb 28 2025 Antonio Murdaca - 0.15.8-3 182 | - use autosetup instead of setup 183 | 184 | * Thu Feb 27 2025 Antonio Murdaca - 0.15.8-2 185 | - Keep building ix86 in rhel < 10 186 | 187 | * Tue Feb 18 2025 Antonio Murdaca - 0.15.8-1 188 | - Bump to 0.15.8 189 | - Fail early if a required check fails 190 | - Fix rollback if bootc is installed 191 | 192 | * Thu Oct 31 2024 Sayan Paul - 0.15.7-1 193 | - Update to 0.15.7 194 | - Reword warning message for disabled checks 195 | - Fixed the issue that boot_counter cannot be unset and some scripts do not have executable permissions 196 | - Packit: only use IoT relevant branches 197 | 198 | * Tue Sep 10 2024 Paul Whalen - 0.15.6-1 199 | - Update to 0.15.6 200 | 201 | * Tue Sep 10 2024 Paul Whalen - 0.15.5-3 202 | - Moved greenboot config to /etc/grub.d. 203 | - %post symlink greenboot.cfg to bootupd if present 204 | - %postun remove symlink from bootupd if present 205 | 206 | * Thu Aug 22 2024 Peter Robinson - 0.15.5-2 207 | - Reorder files, don't overwrite configs on update 208 | 209 | * Fri Aug 16 2024 saypaul - 0.15.5-1 210 | - The 0.15.5 release 211 | - Auto-detect image type and use correct rollback 212 | - Support for read only /boot mount 213 | - Warn users of missing disabled healthchecks 214 | - Add feature to disable healthchecks 215 | 216 | * Fri Feb 17 2023 Paul Whalen - 0.15.4-1 217 | - The 0.15.4 release 218 | - Fix update_platforms_check script 219 | 220 | * Mon Nov 28 2022 Paul Whalen - 0.15.3-1 221 | - The 0.15.3 release 222 | - revert service-monitor 223 | 224 | * Thu Sep 08 2022 Peter Robinson - 0.15.2-1 225 | - The 0.15.2 release 226 | 227 | * Tue Aug 09 2022 Peter Robinson - 0.15.1-1 228 | - Add conf during installation 229 | 230 | * Thu Jul 21 2022 Sayan Paul - 0.15.0-1 231 | - The 0.15.0 release 232 | - Add service-monitor 233 | 234 | * Thu Nov 18 2021 Peter Robinson - 0.14.0-1 235 | - The 0.14.0 release 236 | - Add watchdog-triggered boot check 237 | - Ensure all required health checks are run 238 | 239 | * Wed Nov 10 2021 Peter Robinson - 0.13.1-1 240 | - Update to 0.13.1 241 | 242 | * Mon Jul 26 2021 Jose Noguera - 0.12.0-1 243 | - Update to 0.12.0 244 | - Add ability to configure maximum number of boot attempts via env var and config file. 245 | - Add How does it work section to README. 246 | - Add CI via GitHub Actions and unit testing with BATS. 247 | - Add update platforms DNS resolution and connection checker as health checks out of the box 248 | 249 | * Sat Jan 16 2021 Peter Robinson - 0.11.0-2 250 | - Make arch specific due to grub availability on s390x 251 | - Resolves: rhbz#1915241 252 | 253 | * Thu Aug 13 2020 Christian Glombek - 0.11.0-1 254 | - Update to 0.11.0 255 | 256 | * Thu Jun 11 2020 Peter Robinson - 0.10.3-2 257 | - Update changelog 258 | 259 | * Fri Jun 05 2020 Christian Glombek - 0.10.3-1 260 | - Update to 0.10.3 261 | 262 | * Wed Jun 03 2020 Christian Glombek - 0.10.2-1 263 | - Update to 0.10.2 264 | 265 | * Wed May 27 2020 Christian Glombek - 0.10-1 266 | - Update to 0.10 267 | 268 | * Mon May 04 2020 Christian Glombek - 0.9-2 269 | - Added missing requires to grub2 and rpm-ostree-grub2 packages 270 | - Run %%setup quietly 271 | 272 | * Fri Apr 03 2020 Christian Glombek - 0.9-1 273 | - Update to v0.9 274 | - Update repo_owner 275 | 276 | * Wed Feb 05 2020 Christian Glombek - 0.8-1 277 | - Update to v0.8 278 | - Add guard against bootlooping in redboot-auto-reboot.service 279 | 280 | * Mon Apr 01 2019 Christian Glombek - 0.7-1 281 | - Update to v0.7 282 | - Rename ostree-grub2 subpackage to rpm-ostree-grub2 to be more explicit 283 | - Add auto-update-fallback meta subpackage 284 | 285 | * Wed Feb 13 2019 Christian Glombek - 0.6-1 286 | - Update to v0.6 287 | - Integrate with systemd's boot-complete.target 288 | - Rewrite motd sub-package and rename to status 289 | 290 | * Fri Oct 19 2018 Christian Glombek - 0.5-1 291 | - Update to v0.5 292 | 293 | * Tue Oct 02 2018 Christian Glombek - 0.4-2 294 | - Spec Review 295 | 296 | * Thu Jun 14 2018 Christian Glombek - 0.4-1 297 | - Initial Package 298 | -------------------------------------------------------------------------------- /tests/greenboot-bootc-qcow2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euox pipefail 3 | 4 | # Dumps details about the instance running the CI job. 5 | echo -e "\033[0;36m" 6 | cat << EOF 7 | ------------------------------------------------------------------------------ 8 | CI MACHINE SPECS 9 | ------------------------------------------------------------------------------ 10 | Hostname: $(uname -n) 11 | User: $(whoami) 12 | CPUs: $(nproc) 13 | RAM: $(free -m | grep -oP '\d+' | head -n 1) MB 14 | DISK: $(df --output=size -h / | sed '1d;s/[^0-9]//g') GB 15 | ARCH: $(uname -m) 16 | KERNEL: $(uname -r) 17 | ------------------------------------------------------------------------------ 18 | EOF 19 | echo -e "\033[0m" 20 | 21 | # Get OS info 22 | source /etc/os-release 23 | 24 | # Setup variables 25 | TEST_UUID=qcow2-$((1 + RANDOM % 1000000)) 26 | TEMPDIR=$(mktemp -d) 27 | GUEST_ADDRESS=192.168.100.50 28 | SSH_OPTIONS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5) 29 | SSH_KEY=key/ostree_key 30 | SSH_KEY_PUB=$(cat "${SSH_KEY}".pub) 31 | EDGE_USER=core 32 | EDGE_USER_PASSWORD=foobar 33 | 34 | case "${ID}-${VERSION_ID}" in 35 | "fedora-41") 36 | OS_VARIANT="fedora-unknown" 37 | BASE_IMAGE_URL="quay.io/fedora/fedora-bootc:41" 38 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 39 | BOOT_ARGS="uefi" 40 | ;; 41 | "fedora-42") 42 | OS_VARIANT="fedora-unknown" 43 | BASE_IMAGE_URL="quay.io/fedora/fedora-bootc:42" 44 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 45 | BOOT_ARGS="uefi" 46 | ;; 47 | "centos-9") 48 | OS_VARIANT="centos-stream9" 49 | BASE_IMAGE_URL="quay.io/centos-bootc/centos-bootc:stream9" 50 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 51 | BOOT_ARGS="uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no" 52 | ;; 53 | "centos-10") 54 | OS_VARIANT="centos-stream9" 55 | BASE_IMAGE_URL="quay.io/centos-bootc/centos-bootc:stream10" 56 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 57 | BOOT_ARGS="uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no" 58 | ;; 59 | "rhel-9.6") 60 | OS_VARIANT="rhel9-unknown" 61 | BASE_IMAGE_URL="registry.stage.redhat.io/rhel9/rhel-bootc:9.6" 62 | BIB_URL="registry.stage.redhat.io/rhel9/bootc-image-builder:9.6" 63 | BOOT_ARGS="uefi" 64 | ;; 65 | "rhel-10.0") 66 | OS_VARIANT="rhel10-unknown" 67 | BASE_IMAGE_URL="registry.stage.redhat.io/rhel10/rhel-bootc:10.0" 68 | BIB_URL="registry.stage.redhat.io/rhel10/bootc-image-builder:10.0" 69 | BOOT_ARGS="uefi" 70 | ;; 71 | *) 72 | echo "unsupported distro: ${ID}-${VERSION_ID}" 73 | exit 1;; 74 | esac 75 | 76 | # Colorful output. 77 | function greenprint { 78 | echo -e "\033[1;32m${1}\033[0m" 79 | } 80 | 81 | check_result () { 82 | greenprint "🎏 Checking for test result" 83 | if [[ $RESULTS == 1 ]]; then 84 | greenprint "💚 Success" 85 | else 86 | greenprint "❌ Failed" 87 | exit 1 88 | fi 89 | } 90 | 91 | # Wait for the ssh server up to be. 92 | wait_for_ssh_up () { 93 | SSH_STATUS=$(sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" ${EDGE_USER}@"${1}" '/bin/bash -c "echo -n READY"') 94 | if [[ $SSH_STATUS == READY ]]; then 95 | echo 1 96 | else 97 | echo 0 98 | fi 99 | } 100 | 101 | ########################################################### 102 | ## 103 | ## Prepare before run test 104 | ## 105 | ########################################################### 106 | greenprint "Installing required packages" 107 | sudo dnf install -y podman qemu-img firewalld qemu-kvm libvirt-client libvirt-daemon-kvm libvirt-daemon virt-install rpmdevtools ansible-core 108 | ansible-galaxy collection install community.general 109 | 110 | # Start firewalld 111 | greenprint "Start firewalld" 112 | sudo systemctl enable --now firewalld 113 | 114 | # Check ostree_key permissions 115 | KEY_PERMISSION_PRE=$(stat -L -c "%a %G %U" key/ostree_key | grep -oP '\d+' | head -n 1) 116 | echo -e "${KEY_PERMISSION_PRE}" 117 | if [[ "${KEY_PERMISSION_PRE}" != "600" ]]; then 118 | greenprint "💡 File permissions too open...Changing to 600" 119 | chmod 600 ./key/ostree_key 120 | fi 121 | 122 | # Setup libvirt 123 | greenprint "Starting libvirt service and configure libvirt network" 124 | sudo tee /etc/polkit-1/rules.d/50-libvirt.rules > /dev/null << EOF 125 | polkit.addRule(function(action, subject) { 126 | if (action.id == "org.libvirt.unix.manage" && 127 | subject.isInGroup("adm")) { 128 | return polkit.Result.YES; 129 | } 130 | }); 131 | EOF 132 | sudo systemctl start libvirtd 133 | sudo virsh list --all > /dev/null 134 | sudo tee /tmp/integration.xml > /dev/null << EOF 135 | 136 | integration 137 | 1c8fe98c-b53a-4ca4-bbdb-deb0f26b3579 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | EOF 160 | if ! sudo virsh net-info integration > /dev/null 2>&1; then 161 | sudo virsh net-define /tmp/integration.xml 162 | fi 163 | if [[ $(sudo virsh net-info integration | grep 'Active' | awk '{print $2}') == 'no' ]]; then 164 | sudo virsh net-start integration 165 | fi 166 | 167 | ########################################################### 168 | ## 169 | ## Build greenboot rpm packages 170 | ## 171 | ########################################################### 172 | greenprint "Building greenboot packages" 173 | pushd .. && \ 174 | shopt -s extglob 175 | version=$(cat greenboot.spec |grep Version|awk '{print $2}') 176 | rm -rf greenboot-${version}/ rpmbuild/ 177 | mkdir -p rpmbuild/BUILD rpmbuild/RPMS rpmbuild/SOURCES rpmbuild/SPECS rpmbuild/SRPMS 178 | mkdir greenboot-${version} 179 | cp -r !(rpmbuild|greenboot-${version}|build.sh) greenboot-${version}/ 180 | tar -cvf v${version}.tar.gz greenboot-${version}/ 181 | mv v${version}.tar.gz rpmbuild/SOURCES/ 182 | rpmbuild -bb --define="_topdir ${PWD}/rpmbuild" greenboot.spec 183 | chmod +x rpmbuild/RPMS/x86_64/*.rpm && \ 184 | cp rpmbuild/RPMS/x86_64/*.rpm tests/ && popd 185 | 186 | ########################################################### 187 | ## 188 | ## Build bootc container with greenboot installed 189 | ## 190 | ########################################################### 191 | greenprint "Building bootc container with greenboot installed" 192 | podman login quay.io -u ${QUAY_USERNAME} -p ${QUAY_PASSWORD} 193 | podman login registry.stage.redhat.io -u ${STAGE_REDHAT_IO_USERNAME} -p ${STAGE_REDHAT_IO_TOKEN} 194 | tee Containerfile > /dev/null << EOF 195 | FROM ${BASE_IMAGE_URL} 196 | # Copy the local RPM files into the container 197 | COPY greenboot-*.rpm /tmp/ 198 | RUN dnf install -y \ 199 | /tmp/greenboot-*.rpm && \ 200 | systemctl enable greenboot-grub2-set-counter \ 201 | greenboot-grub2-set-success.service greenboot-healthcheck.service \ 202 | greenboot-loading-message.service greenboot-rpm-ostree-grub2-check-fallback.service \ 203 | redboot-auto-reboot.service redboot-task-runner.service redboot.target 204 | # Clean up by removing the local RPMs if desired 205 | RUN rm -f /tmp/greenboot-*.rpm 206 | EOF 207 | podman build --retry=5 --retry-delay=10s -t quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} -f Containerfile . 208 | greenprint "Pushing bootc container to quay.io" 209 | podman push quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 210 | 211 | ########################################################### 212 | ## 213 | ## BIB to convert bootc container to qcow2/iso images 214 | ## 215 | ########################################################### 216 | greenprint "Using BIB to convert container to qcow2" 217 | tee config.json > /dev/null << EOF 218 | { 219 | "blueprint": { 220 | "customizations": { 221 | "user": [ 222 | { 223 | "name": "${EDGE_USER}", 224 | "password": "${EDGE_USER_PASSWORD}", 225 | "key": "${SSH_KEY_PUB}", 226 | "groups": [ 227 | "wheel" 228 | ] 229 | } 230 | ] 231 | } 232 | } 233 | } 234 | EOF 235 | sudo rm -fr output && mkdir -p output 236 | podman run \ 237 | --rm \ 238 | -it \ 239 | --privileged \ 240 | --pull=newer \ 241 | --security-opt label=type:unconfined_t \ 242 | -v $(pwd)/config.json:/config.json \ 243 | -v $(pwd)/output:/output \ 244 | -v /var/lib/containers/storage:/var/lib/containers/storage \ 245 | ${BIB_URL} \ 246 | --type qcow2 \ 247 | --config /config.json \ 248 | --rootfs xfs \ 249 | --use-librepo=true \ 250 | quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 251 | 252 | ########################################################### 253 | ## 254 | ## Provision vm with qcow2/iso artifacts 255 | ## 256 | ########################################################### 257 | greenprint "Installing vm with bootc qcow2/iso image" 258 | mv $(pwd)/output/qcow2/disk.qcow2 /var/lib/libvirt/images/${TEST_UUID}-disk.qcow2 259 | LIBVIRT_IMAGE_PATH_UEFI=/var/lib/libvirt/images/${TEST_UUID}-disk.qcow2 260 | sudo restorecon -Rv /var/lib/libvirt/images/ 261 | sudo virt-install --name="${TEST_UUID}-uefi"\ 262 | --disk path="${LIBVIRT_IMAGE_PATH_UEFI}",format=qcow2 \ 263 | --ram 3072 \ 264 | --vcpus 2 \ 265 | --network network=integration,mac=34:49:22:B0:83:30 \ 266 | --os-type linux \ 267 | --os-variant ${OS_VARIANT} \ 268 | --boot ${BOOT_ARGS} \ 269 | --nographics \ 270 | --noautoconsole \ 271 | --wait=-1 \ 272 | --import \ 273 | --noreboot 274 | greenprint "Starting UEFI VM" 275 | sudo virsh start "${TEST_UUID}-uefi" 276 | 277 | # Check for ssh ready to go. 278 | greenprint "🛃 Checking for SSH is ready to go" 279 | for _ in $(seq 0 30); do 280 | RESULTS="$(wait_for_ssh_up $GUEST_ADDRESS)" 281 | if [[ $RESULTS == 1 ]]; then 282 | echo "SSH is ready now! 🥳" 283 | break 284 | fi 285 | sleep 10 286 | done 287 | check_result 288 | 289 | ########################################################### 290 | ## 291 | ## Build upgrade container with failing-unit installed 292 | ## 293 | ########################################################### 294 | greenprint "Building upgrade container" 295 | tee Containerfile > /dev/null << EOF 296 | FROM quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 297 | RUN dnf install -y https://kite-webhook-prod.s3.amazonaws.com/greenboot-failing-unit-1.0-1.el8.noarch.rpm 298 | EOF 299 | podman build --retry=5 --retry-delay=10s -t quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} -f Containerfile . 300 | greenprint "Pushing upgrade container to quay.io" 301 | podman push quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 302 | 303 | ########################################################### 304 | ## 305 | ## Bootc upgrade and check if greenboot can rollback 306 | ## 307 | ########################################################### 308 | greenprint "Bootc upgrade and reboot" 309 | sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" ${EDGE_USER}@${GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |sudo -S bootc upgrade" 310 | sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" ${EDGE_USER}@${GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |nohup sudo -S systemctl reboot &>/dev/null & exit" 311 | 312 | # Wait vm to finish the fallback 313 | sleep 240 314 | 315 | # Check for ssh ready to go. 316 | greenprint "🛃 Checking for SSH is ready to go" 317 | for _ in $(seq 0 30); do 318 | RESULTS="$(wait_for_ssh_up $GUEST_ADDRESS)" 319 | if [[ $RESULTS == 1 ]]; then 320 | echo "SSH is ready now! 🥳" 321 | break 322 | fi 323 | sleep 10 324 | done 325 | check_result 326 | 327 | # Add instance IP address into /etc/ansible/hosts 328 | tee ${TEMPDIR}/inventory > /dev/null << EOF 329 | [greenboot_guest] 330 | ${GUEST_ADDRESS} 331 | 332 | [greenboot_guest:vars] 333 | ansible_python_interpreter=/usr/bin/python3 334 | ansible_user=${EDGE_USER} 335 | ansible_private_key_file=${SSH_KEY} 336 | ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" 337 | ansible_become=yes 338 | ansible_become_method=sudo 339 | ansible_become_pass=${EDGE_USER_PASSWORD} 340 | EOF 341 | 342 | # Test greenboot functionality 343 | ansible-playbook -v -i ${TEMPDIR}/inventory greenboot-bootc.yaml || RESULTS=0 344 | 345 | # Test result checking 346 | check_result 347 | exit 0 348 | -------------------------------------------------------------------------------- /tests/check-ostree.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: ostree_guest 3 | become: no 4 | vars: 5 | total_counter: "0" 6 | failed_counter: "0" 7 | 8 | tasks: 9 | 10 | # current target host's IP address 11 | - debug: var=ansible_all_ipv4_addresses 12 | - debug: var=ansible_facts['distribution_version'] 13 | - debug: var=ansible_facts['distribution'] 14 | - debug: var=ansible_facts['architecture'] 15 | 16 | # check BIOS or UEFI 17 | - name: check bios or uefi 18 | stat: 19 | path: /sys/firmware/efi 20 | 21 | # check secure boot status if it's enabled 22 | - name: check secure boot status 23 | command: mokutil --sb-state 24 | ignore_errors: yes 25 | 26 | - name: check partition size 27 | command: df -h 28 | ignore_errors: yes 29 | become: yes 30 | 31 | - name: check disk partition table 32 | command: fdisk -l 33 | ignore_errors: yes 34 | become: yes 35 | 36 | - name: check rpm-ostree status 37 | command: rpm-ostree status 38 | ignore_errors: yes 39 | 40 | - name: check installed kernel 41 | command: uname -r 42 | register: result_kernel 43 | 44 | # first installed or upgraded 45 | - name: determin which stage the checking is running on 46 | shell: rpm-ostree status --json | jq '.deployments | length' 47 | register: result_stage 48 | 49 | - set_fact: 50 | checking_stage: "{{ result_stage.stdout }}" 51 | 52 | # case: check ostree commit correctly updated 53 | - name: get deployed ostree commit 54 | shell: rpm-ostree status --json | jq -r '.deployments[0].checksum' 55 | register: result_commit 56 | 57 | - name: make a json result 58 | set_fact: 59 | deploy_commit: "{{ result_commit.stdout }}" 60 | 61 | - name: check commit deployed and built 62 | block: 63 | - assert: 64 | that: 65 | - deploy_commit == ostree_commit 66 | fail_msg: "deployed ostree commit is not commit built by osbuild-composer" 67 | success_msg: "successful building and deployment" 68 | always: 69 | - set_fact: 70 | total_counter: "{{ total_counter | int + 1 }}" 71 | rescue: 72 | - name: failed count + 1 73 | set_fact: 74 | failed_counter: "{{ failed_counter | int + 1 }}" 75 | 76 | # case: check ostree ref 77 | - name: check ostree ref 78 | shell: rpm-ostree status --json | jq -r '.deployments[0].origin' 79 | register: result_ref 80 | 81 | - name: check ostree ref deployed 82 | block: 83 | - assert: 84 | that: 85 | - result_ref.stdout == ostree_ref 86 | fail_msg: "deployed ostree ref failed" 87 | success_msg: "ostree ref successful building and deployment" 88 | always: 89 | - set_fact: 90 | total_counter: "{{ total_counter | int + 1 }}" 91 | rescue: 92 | - name: failed count + 1 93 | set_fact: 94 | failed_counter: "{{ failed_counter | int + 1 }}" 95 | 96 | - name: check mount point device name 97 | command: findmnt 98 | 99 | # case: check wget installed after upgrade 100 | - name: check installed package 101 | shell: rpm -qa | sort 102 | register: result_packages 103 | 104 | - name: check wget installed 105 | block: 106 | - assert: 107 | that: 108 | - "'wget' in result_packages.stdout" 109 | fail_msg: "wget not installed, ostree upgrade might be failed" 110 | success_msg: "wget installed in ostree upgrade" 111 | always: 112 | - set_fact: 113 | total_counter: "{{ total_counter | int + 1 }}" 114 | rescue: 115 | - name: failed count + 1 116 | set_fact: 117 | failed_counter: "{{ failed_counter | int + 1 }}" 118 | when: checking_stage == "2" 119 | 120 | # case: check installed greenboot packages 121 | - name: greenboot should be installed 122 | block: 123 | - name: greenboot should be installed 124 | shell: rpm -qa | grep greenboot 125 | register: result_greenboot_packages 126 | 127 | - assert: 128 | that: 129 | - "'greenboot-1' in result_greenboot_packages.stdout" 130 | - "'greenboot-default-health-checks' in result_greenboot_packages.stdout" 131 | fail_msg: "greenboot is not installed" 132 | success_msg: "greenboot is installed" 133 | always: 134 | - set_fact: 135 | total_counter: "{{ total_counter | int + 1 }}" 136 | rescue: 137 | - name: failed count + 1 138 | set_fact: 139 | failed_counter: "{{ failed_counter | int + 1 }}" 140 | 141 | # case: check greenboot services 142 | - name: greenboot services should be enabled 143 | block: 144 | - name: greenboot services should be enabled 145 | command: systemctl is-enabled greenboot-healthcheck.service greenboot-rollback.service 146 | register: result_greenboot_service 147 | 148 | - assert: 149 | that: 150 | - result_greenboot_service.stdout == 'enabled\nenabled' 151 | fail_msg: "greenboot services are not enabled" 152 | success_msg: "greenboot services are enabled" 153 | always: 154 | - set_fact: 155 | total_counter: "{{ total_counter | int + 1 }}" 156 | rescue: 157 | - name: failed count + 1 158 | set_fact: 159 | failed_counter: "{{ failed_counter | int + 1 }}" 160 | 161 | - name: greenboot-healthcheck service should be active 162 | block: 163 | - name: greenboot-healthcheck service should be active 164 | command: systemctl is-active greenboot-healthcheck.service 165 | register: result_greenboot_service 166 | 167 | - assert: 168 | that: 169 | - result_greenboot_service.stdout == 'active' 170 | fail_msg: "greenboot services are not active" 171 | success_msg: "greenboot services are active" 172 | always: 173 | - set_fact: 174 | total_counter: "{{ total_counter | int + 1 }}" 175 | rescue: 176 | - name: failed count + 1 177 | set_fact: 178 | failed_counter: "{{ failed_counter | int + 1 }}" 179 | 180 | # case: check greenboot and greenboot-rollback services log 181 | - name: greenboot service should run without error 182 | block: 183 | - name: all greenboot and greenboot-healthcheck services should run without error 184 | command: journalctl -b -0 -u greenboot -u greenboot-healthcheck 185 | become: yes 186 | register: result_greenboot_log 187 | 188 | - assert: 189 | that: 190 | - "'greenboot health-check passed' in result_greenboot_log.stdout" 191 | fail_msg: "Some errors happened in service boot" 192 | success_msg: "All greenboot services booted success" 193 | 194 | always: 195 | - set_fact: 196 | total_counter: "{{ total_counter | int + 1 }}" 197 | rescue: 198 | - name: failed count + 1 199 | set_fact: 200 | failed_counter: "{{ failed_counter | int + 1 }}" 201 | 202 | # case: check grubenv variables 203 | - name: grubenv variables should contain boot_success=1 204 | block: 205 | - name: grubenv variables should contain boot_success=1 206 | command: grub2-editenv list 207 | register: result_grubenv 208 | become: yes 209 | 210 | - assert: 211 | that: 212 | - "'boot_success=1' in result_grubenv.stdout" 213 | fail_msg: "Not found boot_success=1" 214 | success_msg: "Found boot_success=1" 215 | always: 216 | - set_fact: 217 | total_counter: "{{ total_counter | int + 1 }}" 218 | rescue: 219 | - name: failed count + 1 220 | set_fact: 221 | failed_counter: "{{ failed_counter | int + 1 }}" 222 | 223 | # case: check rollback function if boot error found 224 | - name: install sanely failing health check unit to test red boot status behavior 225 | block: 226 | - name: install sanely failing health check unit to test red boot status behavior 227 | command: rpm-ostree install --cache-only https://kite-webhook-prod.s3.amazonaws.com/greenboot-failing-unit-1.0-1.el8.noarch.rpm --reboot 228 | become: yes 229 | ignore_errors: yes 230 | ignore_unreachable: yes 231 | 232 | - block: 233 | - name: delay 60 seconds after reboot to make system stable 234 | pause: 235 | seconds: 60 236 | delegate_to: 127.0.0.1 237 | 238 | - name: wait for connection to become reachable/usable 239 | wait_for_connection: 240 | delay: 30 241 | register: result_rebooting 242 | 243 | - name: waits until instance is reachable 244 | wait_for: 245 | host: "{{ ansible_all_ipv4_addresses[0] }}" 246 | port: 22 247 | search_regex: OpenSSH 248 | delay: 10 249 | register: result_rollback 250 | until: result_rollback is success 251 | retries: 6 252 | delay: 10 253 | ignore_unreachable: yes 254 | 255 | - fail: 256 | msg: "Failed here for unreachable to run rescue" 257 | when: result_rollback.unreachable is defined 258 | 259 | rescue: 260 | # manual reboot VM to workaround vm reboot failed issue 261 | - name: check vm name 262 | community.libvirt.virt: 263 | command: list_vms 264 | become: yes 265 | register: all_vms 266 | delegate_to: 127.0.0.1 267 | 268 | - set_fact: 269 | vm_name: "{{ item }}" 270 | loop: "{{ all_vms.list_vms }}" 271 | when: item is match ("ostree-.*") 272 | 273 | - debug: var=vm_name 274 | 275 | - name: stop vm "{{ vm_name }}" 276 | community.libvirt.virt: 277 | name: "{{ vm_name }}" 278 | command: destroy 279 | become: yes 280 | delegate_to: 127.0.0.1 281 | 282 | - name: start vm "{{ vm_name }}" 283 | community.libvirt.virt: 284 | name: "{{ vm_name }}" 285 | command: start 286 | become: yes 287 | delegate_to: 127.0.0.1 288 | 289 | - name: wait for connection to become reachable/usable 290 | wait_for_connection: 291 | delay: 30 292 | 293 | - name: waits until instance is reachable 294 | wait_for: 295 | host: "{{ ansible_all_ipv4_addresses[0] }}" 296 | port: 22 297 | search_regex: OpenSSH 298 | delay: 10 299 | register: result_rollback 300 | until: result_rollback is success 301 | retries: 6 302 | delay: 10 303 | 304 | - assert: 305 | that: 306 | - result_rollback is succeeded 307 | fail_msg: "Rollback failed" 308 | success_msg: "Rollback success" 309 | always: 310 | - set_fact: 311 | total_counter: "{{ total_counter | int + 1 }}" 312 | rescue: 313 | - name: failed count + 1 314 | set_fact: 315 | failed_counter: "{{ failed_counter | int + 1 }}" 316 | 317 | # case: check ostree commit after rollback 318 | - name: check ostree commit after rollback 319 | block: 320 | - name: check ostree commit after rollback 321 | shell: rpm-ostree status --json | jq -r '.deployments[0].checksum' 322 | register: result_commit 323 | 324 | - assert: 325 | that: 326 | - deploy_commit == ostree_commit 327 | fail_msg: "Not rollbackto last commit" 328 | success_msg: "Rollback success" 329 | always: 330 | - set_fact: 331 | total_counter: "{{ total_counter | int + 1 }}" 332 | rescue: 333 | - name: failed count + 1 334 | set_fact: 335 | failed_counter: "{{ failed_counter | int + 1 }}" 336 | when: result_rollback is succeeded 337 | 338 | # case: check greenboot* services log again 339 | - name: fallback log should be found here 340 | block: 341 | - name: fallback log should be found here 342 | command: journalctl -u greenboot-rollback 343 | become: yes 344 | register: result_greenboot_log 345 | 346 | - assert: 347 | that: 348 | - "'Greenboot will now attempt to rollback' in result_greenboot_log.stdout" 349 | - "'Rollback successful' in result_greenboot_log.stdout" 350 | fail_msg: "Fallback log not found" 351 | success_msg: "Found fallback log" 352 | 353 | always: 354 | - set_fact: 355 | total_counter: "{{ total_counter | int + 1 }}" 356 | rescue: 357 | - name: failed count + 1 358 | set_fact: 359 | failed_counter: "{{ failed_counter | int + 1 }}" 360 | when: result_rollback is succeeded 361 | 362 | # case: check grubenv variables again 363 | - name: grubenv variables should contain boot_success=1 364 | block: 365 | - name: grubenv variables should contain boot_success=1 366 | command: grub2-editenv list 367 | register: result_grubenv 368 | become: yes 369 | 370 | - assert: 371 | that: 372 | - "'boot_success=1' in result_grubenv.stdout" 373 | fail_msg: "Not found boot_success=1" 374 | success_msg: "Found boot_success=1" 375 | always: 376 | - set_fact: 377 | total_counter: "{{ total_counter | int + 1 }}" 378 | rescue: 379 | - name: failed count + 1 380 | set_fact: 381 | failed_counter: "{{ failed_counter | int + 1 }}" 382 | when: result_rollback is succeeded 383 | 384 | - assert: 385 | that: 386 | - failed_counter == "0" 387 | fail_msg: "Run {{ total_counter }} tests, but {{ failed_counter }} of them failed" 388 | success_msg: "Totally {{ total_counter }} test passed" 389 | -------------------------------------------------------------------------------- /tests/greenboot-bootc-anaconda-iso.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euox pipefail 3 | 4 | # Dumps details about the instance running the CI job. 5 | echo -e "\033[0;36m" 6 | cat << EOF 7 | ------------------------------------------------------------------------------ 8 | CI MACHINE SPECS 9 | ------------------------------------------------------------------------------ 10 | Hostname: $(uname -n) 11 | User: $(whoami) 12 | CPUs: $(nproc) 13 | RAM: $(free -m | grep -oP '\d+' | head -n 1) MB 14 | DISK: $(df --output=size -h / | sed '1d;s/[^0-9]//g') GB 15 | ARCH: $(uname -m) 16 | KERNEL: $(uname -r) 17 | ------------------------------------------------------------------------------ 18 | EOF 19 | echo -e "\033[0m" 20 | 21 | # Get OS info 22 | source /etc/os-release 23 | 24 | # Setup variables 25 | TEST_UUID=anaconda-$((1 + RANDOM % 1000000)) 26 | TEMPDIR=$(mktemp -d) 27 | GUEST_ADDRESS=192.168.100.50 28 | SSH_OPTIONS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5) 29 | SSH_KEY=key/ostree_key 30 | SSH_KEY_PUB=$(cat "${SSH_KEY}".pub) 31 | EDGE_USER=core 32 | EDGE_USER_PASSWORD=foobar 33 | 34 | case "${ID}-${VERSION_ID}" in 35 | "fedora-41") 36 | OS_VARIANT="fedora-unknown" 37 | BASE_IMAGE_URL="quay.io/fedora/fedora-bootc:41" 38 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 39 | BOOT_ARGS="uefi" 40 | ;; 41 | "fedora-42") 42 | OS_VARIANT="fedora-unknown" 43 | BASE_IMAGE_URL="quay.io/fedora/fedora-bootc:42" 44 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 45 | BOOT_ARGS="uefi" 46 | ;; 47 | "centos-9") 48 | OS_VARIANT="centos-stream9" 49 | BASE_IMAGE_URL="quay.io/centos-bootc/centos-bootc:stream9" 50 | BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 51 | BOOT_ARGS="uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no" 52 | ;; 53 | "centos-10") 54 | OS_VARIANT="centos-stream9" 55 | BASE_IMAGE_URL="quay.io/centos-bootc/centos-bootc:stream10" 56 | #BIB_URL="quay.io/centos-bootc/bootc-image-builder:latest" 57 | BIB_URL="quay.io/redhat-user-workloads/centos-bootc-tenant/bootc-image-builder/bootc-image-builder@sha256:58885c4bec997686868bc238eb5007b137f1b32d2995bfb689dc74ca329d0cd9" 58 | BOOT_ARGS="uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no" 59 | ;; 60 | "rhel-9.6") 61 | OS_VARIANT="rhel9-unknown" 62 | BASE_IMAGE_URL="registry.stage.redhat.io/rhel9/rhel-bootc:9.6" 63 | BIB_URL="registry.stage.redhat.io/rhel9/bootc-image-builder:9.6" 64 | BOOT_ARGS="uefi" 65 | sed -i "s/REPLACE_ME_HERE/${DOWNLOAD_NODE}/g" files/rhel-9-6.repo 66 | ;; 67 | "rhel-10.0") 68 | OS_VARIANT="rhel10-unknown" 69 | BASE_IMAGE_URL="registry.stage.redhat.io/rhel10/rhel-bootc:10.0" 70 | BIB_URL="registry.stage.redhat.io/rhel10/bootc-image-builder:10.0" 71 | BOOT_ARGS="uefi" 72 | sed -i "s/REPLACE_ME_HERE/${DOWNLOAD_NODE}/g" files/rhel-10-0.repo 73 | ;; 74 | *) 75 | echo "unsupported distro: ${ID}-${VERSION_ID}" 76 | exit 1;; 77 | esac 78 | 79 | # Colorful output. 80 | function greenprint { 81 | echo -e "\033[1;32m${1}\033[0m" 82 | } 83 | 84 | check_result () { 85 | greenprint "🎏 Checking for test result" 86 | if [[ $RESULTS == 1 ]]; then 87 | greenprint "💚 Success" 88 | else 89 | greenprint "❌ Failed" 90 | exit 1 91 | fi 92 | } 93 | 94 | # Wait for the ssh server up to be. 95 | wait_for_ssh_up () { 96 | SSH_STATUS=$(sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" ${EDGE_USER}@"${1}" '/bin/bash -c "echo -n READY"') 97 | if [[ $SSH_STATUS == READY ]]; then 98 | echo 1 99 | else 100 | echo 0 101 | fi 102 | } 103 | 104 | ########################################################### 105 | ## 106 | ## Prepare before run test 107 | ## 108 | ########################################################### 109 | greenprint "Installing required packages" 110 | sudo dnf install -y podman qemu-img firewalld qemu-kvm libvirt-client libvirt-daemon-kvm libvirt-daemon virt-install rpmdevtools ansible-core lorax 111 | ansible-galaxy collection install community.general 112 | 113 | # Start firewalld 114 | greenprint "Start firewalld" 115 | sudo systemctl enable --now firewalld 116 | 117 | # Check ostree_key permissions 118 | KEY_PERMISSION_PRE=$(stat -L -c "%a %G %U" key/ostree_key | grep -oP '\d+' | head -n 1) 119 | echo -e "${KEY_PERMISSION_PRE}" 120 | if [[ "${KEY_PERMISSION_PRE}" != "600" ]]; then 121 | greenprint "💡 File permissions too open...Changing to 600" 122 | chmod 600 ./key/ostree_key 123 | fi 124 | 125 | # Setup libvirt 126 | greenprint "Starting libvirt service and configure libvirt network" 127 | sudo tee /etc/polkit-1/rules.d/50-libvirt.rules > /dev/null << EOF 128 | polkit.addRule(function(action, subject) { 129 | if (action.id == "org.libvirt.unix.manage" && 130 | subject.isInGroup("adm")) { 131 | return polkit.Result.YES; 132 | } 133 | }); 134 | EOF 135 | sudo systemctl start libvirtd 136 | sudo virsh list --all > /dev/null 137 | sudo tee /tmp/integration.xml > /dev/null << EOF 138 | 139 | integration 140 | 1c8fe98c-b53a-4ca4-bbdb-deb0f26b3579 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | EOF 163 | if ! sudo virsh net-info integration > /dev/null 2>&1; then 164 | sudo virsh net-define /tmp/integration.xml 165 | fi 166 | if [[ $(sudo virsh net-info integration | grep 'Active' | awk '{print $2}') == 'no' ]]; then 167 | sudo virsh net-start integration 168 | fi 169 | 170 | ########################################################### 171 | ## 172 | ## Build greenboot rpm packages 173 | ## 174 | ########################################################### 175 | greenprint "Building greenboot packages" 176 | pushd .. && \ 177 | shopt -s extglob 178 | version=$(cat greenboot.spec |grep Version|awk '{print $2}') 179 | rm -rf greenboot-${version}/ rpmbuild/ 180 | mkdir -p rpmbuild/BUILD rpmbuild/RPMS rpmbuild/SOURCES rpmbuild/SPECS rpmbuild/SRPMS 181 | mkdir greenboot-${version} 182 | cp -r !(rpmbuild|greenboot-${version}|build.sh) greenboot-${version}/ 183 | tar -cvf v${version}.tar.gz greenboot-${version}/ 184 | mv v${version}.tar.gz rpmbuild/SOURCES/ 185 | rpmbuild -bb --define="_topdir ${PWD}/rpmbuild" greenboot.spec 186 | chmod +x rpmbuild/RPMS/x86_64/*.rpm && \ 187 | cp rpmbuild/RPMS/x86_64/*.rpm tests/ && popd 188 | 189 | ########################################################### 190 | ## 191 | ## Build bootc container with greenboot installed 192 | ## 193 | ########################################################### 194 | greenprint "Building rhel-edge-bootc container" 195 | podman login quay.io -u ${QUAY_USERNAME} -p ${QUAY_PASSWORD} 196 | podman login registry.stage.redhat.io -u ${STAGE_REDHAT_IO_USERNAME} -p ${STAGE_REDHAT_IO_TOKEN} 197 | tee Containerfile > /dev/null << EOF 198 | FROM ${BASE_IMAGE_URL} 199 | # Copy the local RPM files into the container 200 | COPY greenboot-*.rpm /tmp/ 201 | RUN dnf install -y \ 202 | /tmp/greenboot-*.rpm && \ 203 | systemctl enable greenboot-grub2-set-counter \ 204 | greenboot-grub2-set-success.service greenboot-healthcheck.service \ 205 | greenboot-loading-message.service greenboot-rpm-ostree-grub2-check-fallback.service \ 206 | redboot-auto-reboot.service redboot-task-runner.service redboot.target 207 | # Clean up by removing the local RPMs if desired 208 | RUN rm -f /tmp/greenboot-*.rpm 209 | EOF 210 | 211 | if [[ "${ID}-${VERSION_ID}" == "rhel-9.6" ]]; then 212 | tee -a Containerfile >> /dev/null << EOF 213 | COPY files/rhel-9-6.repo /etc/yum.repos.d/rhel-9-6.repo 214 | EOF 215 | fi 216 | 217 | if [[ "${ID}-${VERSION_ID}" == "rhel-10.0" ]]; then 218 | tee -a Containerfile >> /dev/null << EOF 219 | COPY files/rhel-10-0.repo /etc/yum.repos.d/rhel-10-0.repo 220 | EOF 221 | fi 222 | 223 | podman build --retry=5 --retry-delay=10s -t quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} -f Containerfile . 224 | greenprint "Pushing greenboot-bootc container to quay.io" 225 | podman push quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 226 | 227 | ########################################################### 228 | ## 229 | ## BIB to convert bootc container to qcow2/iso images 230 | ## 231 | ########################################################### 232 | greenprint "Using BIB to convert container to anaconda iso" 233 | tee config.json > /dev/null << EOF 234 | { 235 | "blueprint": { 236 | "customizations": { 237 | "user": [ 238 | { 239 | "name": "${EDGE_USER}", 240 | "password": "${EDGE_USER_PASSWORD}", 241 | "key": "${SSH_KEY_PUB}", 242 | "groups": [ 243 | "wheel" 244 | ] 245 | } 246 | ] 247 | } 248 | } 249 | } 250 | EOF 251 | sudo rm -fr output && mkdir -p output 252 | podman run \ 253 | --rm \ 254 | -it \ 255 | --privileged \ 256 | --pull=newer \ 257 | --security-opt label=type:unconfined_t \ 258 | -v $(pwd)/config.json:/config.json \ 259 | -v $(pwd)/output:/output \ 260 | -v /var/lib/containers/storage:/var/lib/containers/storage \ 261 | ${BIB_URL} \ 262 | --type anaconda-iso \ 263 | --config /config.json \ 264 | --rootfs xfs \ 265 | --use-librepo=true \ 266 | quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 267 | 268 | ########################################################### 269 | ## 270 | ## Generate kickstart file and mkksiso 271 | ## 272 | ########################################################### 273 | ISOMOUNT=$(mktemp -d) 274 | greenprint "Mounting install.iso -> $ISOMOUNT" 275 | sudo mount -v -o ro "output/bootiso/install.iso" "$ISOMOUNT" 276 | NEW_KS_FILE="${TEMPDIR}/bib.ks" 277 | greenprint "Default osbuild-base.ks" 278 | cat "${ISOMOUNT}/osbuild-base.ks" 279 | greenprint "Default osbuild.ks" 280 | cat "${ISOMOUNT}/osbuild.ks" 281 | 282 | greenprint "Preparing modified kickstart file" 283 | cat > "$NEW_KS_FILE" << EOFKS 284 | text 285 | $(cat "${ISOMOUNT}/osbuild-base.ks") 286 | $(cat "${ISOMOUNT}/osbuild.ks") 287 | EOFKS 288 | sed -i '/%include/d' "$NEW_KS_FILE" 289 | 290 | greenprint "Writing new ISO" 291 | sudo mkksiso -c "console=ttyS0,115200" --ks "$NEW_KS_FILE" "output/bootiso/install.iso" "/var/lib/libvirt/images/install.iso" 292 | 293 | greenprint "==== NEW KICKSTART FILE ====" 294 | cat "$NEW_KS_FILE" 295 | greenprint "============================" 296 | 297 | greenprint "Clean up ISO building environment" 298 | sudo umount -v "$ISOMOUNT" 299 | rm -rf "$ISOMOUNT" 300 | 301 | ########################################################### 302 | ## 303 | ## Provision vm with qcow2/iso artifacts 304 | ## 305 | ########################################################### 306 | greenprint "Installing vm with bootc anaconda iso image" 307 | greenprint "💾 Create vm qcow2 files for ISO installation" 308 | LIBVIRT_IMAGE_PATH_UEFI=/var/lib/libvirt/images/${TEST_UUID}-disk.qcow2 309 | sudo qemu-img create -f qcow2 "/var/lib/libvirt/images/${TEST_UUID}-disk.qcow2" 10G 310 | sudo restorecon -Rv /var/lib/libvirt/images/ 311 | sudo virt-install --name="${TEST_UUID}-uefi"\ 312 | --disk path="${LIBVIRT_IMAGE_PATH_UEFI}",format=qcow2 \ 313 | --ram 3072 \ 314 | --vcpus 2 \ 315 | --network network=integration,mac=34:49:22:B0:83:30 \ 316 | --os-type linux \ 317 | --os-variant ${OS_VARIANT} \ 318 | --cdrom "/var/lib/libvirt/images/install.iso" \ 319 | --boot ${BOOT_ARGS} \ 320 | --nographics \ 321 | --noautoconsole \ 322 | --wait=-1 \ 323 | --noreboot 324 | greenprint "Starting UEFI VM" 325 | sudo virsh start "${TEST_UUID}-uefi" 326 | 327 | # Check for ssh ready to go. 328 | greenprint "🛃 Checking for SSH is ready to go" 329 | for _ in $(seq 0 30); do 330 | RESULTS="$(wait_for_ssh_up $GUEST_ADDRESS)" 331 | if [[ $RESULTS == 1 ]]; then 332 | echo "SSH is ready now! 🥳" 333 | break 334 | fi 335 | sleep 10 336 | done 337 | check_result 338 | 339 | ########################################################### 340 | ## 341 | ## Build upgrade container with failing-unit installed 342 | ## 343 | ########################################################### 344 | greenprint "Building upgrade container" 345 | tee Containerfile > /dev/null << EOF 346 | FROM quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 347 | RUN dnf install -y https://kite-webhook-prod.s3.amazonaws.com/greenboot-failing-unit-1.0-1.el8.noarch.rpm 348 | EOF 349 | podman build --retry=5 --retry-delay=10s -t quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} -f Containerfile . 350 | greenprint "Pushing upgrade container to quay.io" 351 | podman push quay.io/${QUAY_USERNAME}/greenboot-bootc:${TEST_UUID} 352 | 353 | ########################################################### 354 | ## 355 | ## Bootc upgrade and check if greenboot can rollback 356 | ## 357 | ########################################################### 358 | greenprint "Bootc upgrade and reboot" 359 | sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" ${EDGE_USER}@${GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |sudo -S bootc upgrade" 360 | sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" ${EDGE_USER}@${GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |nohup sudo -S systemctl reboot &>/dev/null & exit" 361 | 362 | # Wait vm to finish the fallback 363 | sleep 240 364 | 365 | # Check for ssh ready to go. 366 | greenprint "🛃 Checking for SSH is ready to go" 367 | for _ in $(seq 0 30); do 368 | RESULTS="$(wait_for_ssh_up $GUEST_ADDRESS)" 369 | if [[ $RESULTS == 1 ]]; then 370 | echo "SSH is ready now! 🥳" 371 | break 372 | fi 373 | sleep 10 374 | done 375 | check_result 376 | 377 | # Add instance IP address into /etc/ansible/hosts 378 | tee ${TEMPDIR}/inventory > /dev/null << EOF 379 | [greenboot_guest] 380 | ${GUEST_ADDRESS} 381 | 382 | [greenboot_guest:vars] 383 | ansible_python_interpreter=/usr/bin/python3 384 | ansible_user=${EDGE_USER} 385 | ansible_private_key_file=${SSH_KEY} 386 | ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" 387 | ansible_become=yes 388 | ansible_become_method=sudo 389 | ansible_become_pass=${EDGE_USER_PASSWORD} 390 | EOF 391 | 392 | # Test greenboot functionality 393 | ansible-playbook -v -i /${TEMPDIR}/inventory greenboot-bootc.yaml || RESULTS=0 394 | 395 | # Test result checking 396 | check_result 397 | exit 0 398 | -------------------------------------------------------------------------------- /tests/files/rhel-9-6-0.json: -------------------------------------------------------------------------------- 1 | { 2 | "aarch64": [ 3 | { 4 | "name": "baseos", 5 | "baseurl": "http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/BaseOS/aarch64/os/", 6 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----" 7 | }, 8 | { 9 | "name": "appstream", 10 | "baseurl": "http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/AppStream/aarch64/os/", 11 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----" 12 | } 13 | ], 14 | "x86_64": [ 15 | { 16 | "name": "baseos", 17 | "baseurl": "http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/BaseOS/x86_64/os/", 18 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----" 19 | }, 20 | { 21 | "name": "appstream", 22 | "baseurl": "http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/AppStream/x86_64/os/", 23 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----" 24 | }, 25 | { 26 | "name": "rt", 27 | "baseurl": "http://REPLACE_ME_HERE/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/RT/x86_64/os/", 28 | "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----" 29 | }, 30 | { 31 | "name": "packages", 32 | "baseurl": "http://192.168.100.1/packages/" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /tests/greenboot-rpm-ostree.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -exuo pipefail 3 | 4 | # Get OS data. 5 | source /etc/os-release 6 | 7 | # Dumps details about the instance running the CI job. 8 | CPUS=$(nproc) 9 | MEM=$(free -m | grep -oP '\d+' | head -n 1) 10 | DISK=$(df --output=size -h / | sed '1d;s/[^0-9]//g') 11 | HOSTNAME=$(uname -n) 12 | USER=$(whoami) 13 | ARCH=$(uname -m) 14 | KERNEL=$(uname -r) 15 | 16 | echo -e "\033[0;36m" 17 | cat << EOF 18 | ------------------------------------------------------------------------------ 19 | CI MACHINE SPECS 20 | ------------------------------------------------------------------------------ 21 | Hostname: ${HOSTNAME} 22 | User: ${USER} 23 | CPUs: ${CPUS} 24 | RAM: ${MEM} MB 25 | DISK: ${DISK} GB 26 | ARCH: ${ARCH} 27 | KERNEL: ${KERNEL} 28 | ------------------------------------------------------------------------------ 29 | EOF 30 | echo "CPU info" 31 | lscpu 32 | echo -e "\033[0m" 33 | 34 | # Colorful output. 35 | function greenprint { 36 | echo -e "\033[1;32m${1}\033[0m" 37 | } 38 | 39 | # Set up variables. 40 | TEST_UUID=$(uuidgen) 41 | IMAGE_KEY="ostree-${TEST_UUID}" 42 | GUEST_ADDRESS=192.168.100.50 43 | SSH_USER="admin" 44 | OS_NAME="rhel-edge" 45 | IMAGE_TYPE=edge-commit 46 | PROD_REPO_URL=http://192.168.100.1/repo 47 | 48 | # Set up temporary files. 49 | TEMPDIR=$(mktemp -d) 50 | BLUEPRINT_FILE=${TEMPDIR}/blueprint.toml 51 | HTTPD_PATH="/var/www/html" 52 | KS_FILE=${HTTPD_PATH}/ks.cfg 53 | COMPOSE_START=${TEMPDIR}/compose-start-${IMAGE_KEY}.json 54 | COMPOSE_INFO=${TEMPDIR}/compose-info-${IMAGE_KEY}.json 55 | BOOT_ARGS="uefi" 56 | 57 | # SSH setup. 58 | SSH_OPTIONS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5) 59 | SSH_KEY=key/ostree_key 60 | 61 | # set locale to en_US.UTF-8 62 | sudo dnf install -y glibc-langpack-en 63 | sudo localectl set-locale LANG=en_US.UTF-8 64 | 65 | # Install required packages 66 | greenprint "Install required packages" 67 | sudo dnf install -y --nogpgcheck httpd osbuild osbuild-composer composer-cli podman skopeo wget firewalld lorax xorriso curl jq expect qemu-img qemu-kvm libvirt-client libvirt-daemon-kvm libvirt-daemon virt-install rpmdevtools ansible-core createrepo_c 68 | 69 | # Avoid collection installation filed sometime 70 | for _ in $(seq 0 30); do 71 | ansible-galaxy collection install community.general community.libvirt 72 | install_result=$? 73 | if [[ $install_result == 0 ]]; then 74 | break 75 | fi 76 | sleep 10 77 | done 78 | 79 | # Customize repository 80 | sudo mkdir -p /etc/osbuild-composer/repositories 81 | 82 | # Set os-variant and boot location used by virt-install. 83 | case "${ID}-${VERSION_ID}" in 84 | "rhel-9.6") 85 | OSTREE_REF="rhel/9/${ARCH}/edge" 86 | OS_VARIANT="rhel9-unknown" 87 | BOOT_LOCATION="http://${DOWNLOAD_NODE}/rhel-9/nightly/RHEL-9/latest-RHEL-9.6.0/compose/BaseOS/x86_64/os/" 88 | sed "s/REPLACE_ME_HERE/${DOWNLOAD_NODE}/g" files/rhel-9-6-0.json | sudo tee /etc/osbuild-composer/repositories/rhel-96.json > /dev/null;; 89 | "centos-9") 90 | OSTREE_REF="centos/9/${ARCH}/edge" 91 | OS_VARIANT="centos-stream9" 92 | BOOT_ARGS="uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no" 93 | sudo cp files/centos-stream-9.json /etc/osbuild-composer/repositories/centos-9.json;; 94 | *) 95 | echo "unsupported distro: ${ID}-${VERSION_ID}" 96 | exit 1;; 97 | esac 98 | 99 | if [[ "${ID}-${VERSION_ID}" == "centos-9" ]]; then 100 | CURRENT_COMPOSE_CS9=$(curl -s "https://composes.stream.centos.org/production/" | grep -ioE ">CentOS-Stream-9-.*/<" | tr -d '>/<' | tail -1) 101 | BOOT_LOCATION="https://composes.stream.centos.org/production/${CURRENT_COMPOSE_CS9}/compose/BaseOS/x86_64/os/" 102 | fi 103 | 104 | # Build greenboot RPMs 105 | greenprint "Building greenboot packages" 106 | mkdir -p /var/www/html/packages 107 | pushd .. && \ 108 | shopt -s extglob 109 | version=$(cat greenboot.spec |grep Version|awk '{print $2}') 110 | rm -rf greenboot-${version}/ rpmbuild/ 111 | mkdir -p rpmbuild/BUILD rpmbuild/RPMS rpmbuild/SOURCES rpmbuild/SPECS rpmbuild/SRPMS 112 | mkdir greenboot-${version} 113 | cp -r !(rpmbuild|greenboot-${version}|build.sh) greenboot-${version}/ 114 | tar -cvf v${version}.tar.gz greenboot-${version}/ 115 | mv v${version}.tar.gz rpmbuild/SOURCES/ 116 | rpmbuild -bb --define="_topdir ${PWD}/rpmbuild" greenboot.spec 117 | chmod +x rpmbuild/RPMS/x86_64/*.rpm && \ 118 | cp rpmbuild/RPMS/x86_64/*.rpm /var/www/html/packages/ && popd 119 | 120 | sudo createrepo_c /var/www/html/packages 121 | sudo restorecon -Rv /var/www/html/packages 122 | 123 | # Check ostree_key permissions 124 | KEY_PERMISSION_PRE=$(stat -L -c "%a %G %U" key/ostree_key | grep -oP '\d+' | head -n 1) 125 | echo -e "${KEY_PERMISSION_PRE}" 126 | if [[ "${KEY_PERMISSION_PRE}" != "600" ]]; then 127 | greenprint "💡 File permissions too open...Changing to 600" 128 | chmod 600 ./key/ostree_key 129 | fi 130 | 131 | # Start httpd server as prod ostree repo 132 | greenprint "Start httpd service" 133 | sudo systemctl enable --now httpd.service 134 | 135 | # Start osbuild-composer.socket 136 | greenprint "Start osbuild-composer.socket" 137 | sudo systemctl enable --now osbuild-composer.socket 138 | 139 | # Start firewalld 140 | greenprint "Start firewalld" 141 | sudo systemctl enable --now firewalld 142 | 143 | # Start libvirtd and test it. 144 | greenprint "🚀 Starting libvirt daemon" 145 | sudo systemctl start libvirtd 146 | sudo virsh list --all > /dev/null 147 | 148 | # Set a customized dnsmasq configuration for libvirt so we always get the 149 | # same address on bootup. 150 | greenprint "💡 Setup libvirt network" 151 | sudo tee /tmp/integration.xml > /dev/null << EOF 152 | 153 | integration 154 | 1c8fe98c-b53a-4ca4-bbdb-deb0f26b3579 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | EOF 170 | if ! sudo virsh net-info integration > /dev/null 2>&1; then 171 | sudo virsh net-define /tmp/integration.xml 172 | fi 173 | if [[ $(sudo virsh net-info integration | grep 'Active' | awk '{print $2}') == 'no' ]]; then 174 | sudo virsh net-start integration 175 | fi 176 | 177 | # Allow anyone in the wheel group to talk to libvirt. 178 | greenprint "🚪 Allowing users in wheel group to talk to libvirt" 179 | sudo tee /etc/polkit-1/rules.d/50-libvirt.rules > /dev/null << EOF 180 | polkit.addRule(function(action, subject) { 181 | if (action.id == "org.libvirt.unix.manage" && 182 | subject.isInGroup("adm")) { 183 | return polkit.Result.YES; 184 | } 185 | }); 186 | EOF 187 | 188 | # Basic weldr API status checking 189 | sudo composer-cli status show 190 | 191 | # Source checking 192 | sudo composer-cli sources list 193 | for SOURCE in $(sudo composer-cli sources list); do 194 | sudo composer-cli sources info "$SOURCE" 195 | done 196 | 197 | # Get the compose log. 198 | get_compose_log () { 199 | COMPOSE_ID=$1 200 | LOG_FILE=osbuild-${ID}-${VERSION_ID}-${COMPOSE_ID}.log 201 | 202 | # Download the logs. 203 | sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null 204 | } 205 | 206 | # Get the compose metadata. 207 | get_compose_metadata () { 208 | COMPOSE_ID=$1 209 | METADATA_FILE=osbuild-${ID}-${VERSION_ID}-${COMPOSE_ID}.json 210 | 211 | # Download the metadata. 212 | sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null 213 | 214 | # Find the tarball and extract it. 215 | TARBALL=$(basename "$(find . -maxdepth 1 -type f -name "*-metadata.tar")") 216 | sudo tar -xf "$TARBALL" -C "${TEMPDIR}" 217 | sudo rm -f "$TARBALL" 218 | 219 | # Move the JSON file into place. 220 | sudo cat "${TEMPDIR}"/"${COMPOSE_ID}".json | jq -M '.' | tee "$METADATA_FILE" > /dev/null 221 | } 222 | 223 | # Build ostree image. 224 | build_image() { 225 | blueprint_file=$1 226 | blueprint_name=$2 227 | 228 | # Prepare the blueprint for the compose. 229 | greenprint "📋 Preparing blueprint" 230 | sudo composer-cli blueprints push "$blueprint_file" 231 | sudo composer-cli blueprints depsolve "$blueprint_name" 232 | 233 | # Get worker unit file so we can watch the journal. 234 | WORKER_UNIT=$(sudo systemctl list-units | grep -o -E "osbuild.*worker.*\.service") 235 | sudo journalctl -af -n 1 -u "${WORKER_UNIT}" & 236 | WORKER_JOURNAL_PID=$! 237 | 238 | # Start the compose. 239 | greenprint "🚀 Starting compose" 240 | if [[ $blueprint_name == upgrade ]]; then 241 | # composer-cli in Fedora 32 has a different start-ostree arguments 242 | # see https://github.com/weldr/lorax/pull/1051 243 | sudo composer-cli --json compose start-ostree --ref "$OSTREE_REF" "$blueprint_name" $IMAGE_TYPE | tee "$COMPOSE_START" 244 | else 245 | sudo composer-cli --json compose start "$blueprint_name" $IMAGE_TYPE | tee "$COMPOSE_START" 246 | fi 247 | 248 | COMPOSE_ID=$(jq -r '.[0].body.build_id' "$COMPOSE_START") 249 | 250 | # Wait for the compose to finish. 251 | greenprint "⏱ Waiting for compose to finish: ${COMPOSE_ID}" 252 | while true; do 253 | sudo composer-cli --json compose info "${COMPOSE_ID}" | tee "$COMPOSE_INFO" > /dev/null 254 | 255 | COMPOSE_STATUS=$(jq -r '.[0].body.queue_status' "$COMPOSE_INFO") 256 | 257 | # Is the compose finished? 258 | if [[ $COMPOSE_STATUS != RUNNING ]] && [[ $COMPOSE_STATUS != WAITING ]]; then 259 | break 260 | fi 261 | 262 | # Wait 30 seconds and try again. 263 | sleep 5 264 | done 265 | 266 | # Capture the compose logs from osbuild. 267 | greenprint "💬 Getting compose log and metadata" 268 | get_compose_log "$COMPOSE_ID" 269 | get_compose_metadata "$COMPOSE_ID" 270 | 271 | # Did the compose finish with success? 272 | if [[ $COMPOSE_STATUS != FINISHED ]]; then 273 | echo "Something went wrong with the compose. 😢" 274 | exit 1 275 | fi 276 | 277 | # Stop watching the worker journal. 278 | sudo pkill -P ${WORKER_JOURNAL_PID} 279 | } 280 | 281 | # Wait for the ssh server up to be. 282 | wait_for_ssh_up () { 283 | SSH_STATUS=$(sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" "${SSH_USER}@${1}" '/bin/bash -c "echo -n READY"') 284 | if [[ $SSH_STATUS == READY ]]; then 285 | echo 1 286 | else 287 | echo 0 288 | fi 289 | } 290 | 291 | # Clean up our mess. 292 | clean_up () { 293 | greenprint "🧼 Cleaning up" 294 | sudo virsh destroy "${IMAGE_KEY}" 295 | sudo virsh undefine "${IMAGE_KEY}" --nvram 296 | # Remove qcow2 file. 297 | sudo virsh vol-delete --pool images "${IMAGE_KEY}.qcow2" 298 | # Remove "remote" repo. 299 | sudo rm -rf "${HTTPD_PATH}"/{httpboot,repo,compose.json,ks.cfg} 300 | # Remomve tmp dir. 301 | sudo rm -rf "$TEMPDIR" 302 | # Stop httpd 303 | sudo systemctl disable httpd --now 304 | } 305 | 306 | # Test result checking 307 | check_result () { 308 | greenprint "Checking for test result" 309 | if [[ $RESULTS == 1 ]]; then 310 | greenprint "💚 Success" 311 | else 312 | greenprint "❌ Failed" 313 | clean_up 314 | exit 1 315 | fi 316 | } 317 | 318 | ################################################## 319 | ## 320 | ## ostree image/commit installation 321 | ## 322 | ################################################## 323 | 324 | # Write a blueprint for ostree image. 325 | tee "$BLUEPRINT_FILE" > /dev/null << EOF 326 | name = "ostree" 327 | description = "A base ostree image" 328 | version = "0.0.1" 329 | modules = [] 330 | groups = [] 331 | 332 | [[packages]] 333 | name = "python3" 334 | version = "*" 335 | 336 | [[packages]] 337 | name = "sssd" 338 | version = "*" 339 | 340 | [[customizations.user]] 341 | name = "${SSH_USER}" 342 | description = "Administrator account" 343 | password = "\$6\$GRmb7S0p8vsYmXzH\$o0E020S.9JQGaHkszoog4ha4AQVs3sk8q0DvLjSMxoxHBKnB2FBXGQ/OkwZQfW/76ktHd0NX5nls2LPxPuUdl." 344 | key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzxo5dEcS+LDK/OFAfHo6740EyoDM8aYaCkBala0FnWfMMTOq7PQe04ahB0eFLS3IlQtK5bpgzxBdFGVqF6uT5z4hhaPjQec0G3+BD5Pxo6V+SxShKZo+ZNGU3HVrF9p2V7QH0YFQj5B8F6AicA3fYh2BVUFECTPuMpy5A52ufWu0r4xOFmbU7SIhRQRAQz2u4yjXqBsrpYptAvyzzoN4gjUhNnwOHSPsvFpWoBFkWmqn0ytgHg3Vv9DlHW+45P02QH1UFedXR2MqLnwRI30qqtaOkVS+9rE/dhnR+XPpHHG+hv2TgMDAuQ3IK7Ab5m/yCbN73cxFifH4LST0vVG3Jx45xn+GTeHHhfkAfBSCtya6191jixbqyovpRunCBKexI5cfRPtWOitM3m7Mq26r7LpobMM+oOLUm4p0KKNIthWcmK9tYwXWSuGGfUQ+Y8gt7E0G06ZGbCPHOrxJ8lYQqXsif04piONPA/c9Hq43O99KPNGShONCS9oPFdOLRT3U= ostree-image-test" 345 | home = "/home/${SSH_USER}/" 346 | groups = ["wheel"] 347 | EOF 348 | 349 | # Build installation image. 350 | build_image "$BLUEPRINT_FILE" ostree 351 | 352 | # Download the image and extract tar into web server root folder. 353 | greenprint "📥 Downloading and extracting the image" 354 | sudo composer-cli compose image "${COMPOSE_ID}" > /dev/null 355 | IMAGE_FILENAME="${COMPOSE_ID}-commit.tar" 356 | sudo tar -xf "${IMAGE_FILENAME}" -C ${HTTPD_PATH} 357 | sudo rm -f "$IMAGE_FILENAME" 358 | 359 | # Clean compose and blueprints. 360 | greenprint "Clean up osbuild-composer" 361 | sudo composer-cli compose delete "${COMPOSE_ID}" > /dev/null 362 | sudo composer-cli blueprints delete ostree > /dev/null 363 | 364 | # Ensure SELinux is happy with our new images. 365 | greenprint "👿 Running restorecon on image directory" 366 | sudo restorecon -Rv /var/lib/libvirt/images/ 367 | 368 | # Create qcow2 file for virt install. 369 | greenprint "Create qcow2 file for virt install" 370 | LIBVIRT_IMAGE_PATH=/var/lib/libvirt/images/${IMAGE_KEY}.qcow2 371 | sudo qemu-img create -f qcow2 "${LIBVIRT_IMAGE_PATH}" 20G 372 | 373 | # Write kickstart file for ostree image installation. 374 | greenprint "Generate kickstart file" 375 | sudo tee "$KS_FILE" > /dev/null << STOPHERE 376 | text 377 | rootpw --lock --iscrypted locked 378 | network --bootproto=dhcp --device=link --activate --onboot=on 379 | zerombr 380 | clearpart --all --initlabel --disklabel=msdos 381 | autopart --nohome --noswap --type=plain 382 | ostreesetup --nogpg --osname=${OS_NAME} --remote=${OS_NAME} --url=${PROD_REPO_URL} --ref=${OSTREE_REF} 383 | poweroff 384 | 385 | %post --log=/var/log/anaconda/post-install.log --erroronfail 386 | # no sudo password for SSH user 387 | echo -e '${SSH_USER}\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers 388 | %end 389 | STOPHERE 390 | 391 | # Install ostree image via anaconda. 392 | greenprint "Install ostree image via anaconda" 393 | sudo virt-install --name="${IMAGE_KEY}"\ 394 | --initrd-inject="${KS_FILE}" \ 395 | --extra-args="inst.ks=file:/ks.cfg console=ttyS0,115200" \ 396 | --disk path="${LIBVIRT_IMAGE_PATH}",format=qcow2 \ 397 | --ram 3072 \ 398 | --vcpus 2 \ 399 | --network network=integration,mac=34:49:22:B0:83:30 \ 400 | --os-variant ${OS_VARIANT} \ 401 | --boot ${BOOT_ARGS} \ 402 | --location "${BOOT_LOCATION}" \ 403 | --nographics \ 404 | --noautoconsole \ 405 | --wait=-1 \ 406 | --noreboot 407 | 408 | # Start VM. 409 | greenprint "Start VM" 410 | sudo virsh start "${IMAGE_KEY}" 411 | 412 | # Check for ssh ready to go. 413 | greenprint "🛃 Checking for SSH is ready to go" 414 | for _ in $(seq 0 30); do 415 | RESULTS="$(wait_for_ssh_up $GUEST_ADDRESS)" 416 | if [[ $RESULTS == 1 ]]; then 417 | echo "SSH is ready now! 🥳" 418 | break 419 | fi 420 | sleep 10 421 | done 422 | 423 | # Reboot one more time to make /sysroot as RO by new ostree-libs-2022.6-3.el9.x86_64 424 | sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" "${SSH_USER}@${GUEST_ADDRESS}" 'nohup sudo systemctl reboot &>/dev/null & exit' 425 | # Sleep 10 seconds here to make sure vm restarted already 426 | sleep 10 427 | 428 | # Check for ssh ready to go. 429 | greenprint "🛃 Checking for SSH is ready to go" 430 | for _ in $(seq 0 30); do 431 | RESULTS="$(wait_for_ssh_up $GUEST_ADDRESS)" 432 | if [[ $RESULTS == 1 ]]; then 433 | echo "SSH is ready now! 🥳" 434 | break 435 | fi 436 | sleep 10 437 | done 438 | 439 | # Get ostree commit value. 440 | greenprint "Get ostree image commit value" 441 | INSTALL_HASH=$(curl "${PROD_REPO_URL}/refs/heads/${OSTREE_REF}") 442 | 443 | # Add instance IP address into /etc/ansible/hosts 444 | tee "${TEMPDIR}"/inventory > /dev/null << EOF 445 | [ostree_guest] 446 | ${GUEST_ADDRESS} 447 | [ostree_guest:vars] 448 | ansible_python_interpreter=/usr/bin/python3 449 | ansible_user=${SSH_USER} 450 | ansible_private_key_file=${SSH_KEY} 451 | ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" 452 | EOF 453 | 454 | # Test IoT/Edge OS 455 | ansible-playbook -v -i "${TEMPDIR}/inventory" -e ostree_commit="${INSTALL_HASH}" greenboot-rpm-ostree.yaml || RESULTS=0 456 | 457 | # Check image installation result 458 | check_result 459 | 460 | # Final success clean up 461 | clean_up 462 | 463 | exit 0 464 | --------------------------------------------------------------------------------