diff --git a/bootupd.yaml b/bootupd.yaml new file mode 100644 index 0000000..62ebb0d --- /dev/null +++ b/bootupd.yaml @@ -0,0 +1,26 @@ +# KEEP THIS IN SYNC WITH https://github.com/coreos/fedora-coreos-config/blob/testing-devel/manifests/bootupd.yaml +# See also: https://forge.fedoraproject.org/atomic-desktops/config/src/branch/main/bootupd.yaml +# +# Integration with https://github.com/coreos/bootupd +packages: + - bootupd + +postprocess: + - | + #!/bin/bash + set -xeuo pipefail + + # Transforms /usr/lib/ostree-boot into a bootupd-compatible update payload + /usr/bin/bootupctl backend generate-update-metadata + + # Enable migration to a static GRUB config + install -dm0755 /usr/lib/systemd/system/bootloader-update.service.d + cat > /usr/lib/systemd/system/bootloader-update.service.d/migrate-static-grub-config.conf << 'EOF' + [Service] + ExecStart=/usr/bin/bootupctl migrate-static-grub-config + EOF + + echo "enable bootloader-update.service" >> /usr/lib/systemd/system-preset/80-iot.preset + + # Turn permissive mode on for bootupd until all SELinux issues are fixed + semanage permissive --noreload --add bootupd_t diff --git a/ci/validate b/ci/validate new file mode 100755 index 0000000..c9e6a32 --- /dev/null +++ b/ci/validate @@ -0,0 +1,57 @@ +#!/usr/bin/python3 +# Validate basic syntax of shell script and yaml. + +import os +import stat +import subprocess +import yaml + +validated=0 + +def openat(dirfd, name, mode='r'): + def opener(path, flags): + return os.open(path, flags, dir_fd=dirfd) + return open(name, mode, opener=opener) + + +def validate_shell(rootfd, name): + subprocess.check_call(['bash', '-n', name], preexec_fn=lambda: os.fchdir(rootfd)) + global validated + validated +=1 + + +for root, dirs, files, rootfd in os.fwalk('.'): + # Skip folders that do not include content to validate + for d in ['.git', '.github', 'repo', 'cache', 'tmp', 'logs', 'fedora-comps']: + if d in dirs: + dirs.remove(d) + for f in ['.gitlab-ci.yml']: + if f in files: + files.remove(f) + for name in files: + if name.endswith(('.yaml', '.yml')): + print("Validating:", name) + with open(os.open(name, dir_fd=rootfd, flags=os.O_RDONLY)) as f: + yaml.safe_load(f) + result = subprocess.run(['grep', '-RniEv', '^( )*[a-z#/-]|^( )*\\[|^$|^#', name], encoding='UTF-8', + preexec_fn=lambda: os.fchdir(rootfd)) + if result.returncode == 0: + raise Exception("Found likely invalid indentation in YAML file: {}".format(name)) + validated +=1 + continue + elif name.endswith('.sh'): + print("Validating:", name) + validate_shell(rootfd, name) + continue + stbuf = os.lstat(name, dir_fd=rootfd) + if not stat.S_ISREG(stbuf.st_mode): + continue + if not stbuf.st_mode & stat.S_IXUSR: + continue + mimetype = subprocess.check_output(['file', '-b', '--mime-type', name], encoding='UTF-8', + preexec_fn=lambda: os.fchdir(rootfd)).strip() + if mimetype == 'text/x-shellscript': + print("Validating:", name) + validate_shell(rootfd, name) + +print(f"Validated {validated} files") diff --git a/composefs.yaml b/composefs.yaml new file mode 100644 index 0000000..8a721f3 --- /dev/null +++ b/composefs.yaml @@ -0,0 +1,12 @@ +# Enable composefs +# See: https://fedoraproject.org/wiki/Changes/ComposefsAtomicDesktops +# https://fedoraproject.org/wiki/Changes/ComposefsAtomicCoreOSIoT +postprocess: + - | + #!/usr/bin/env bash + set -xeuo pipefail + + cat >> /usr/lib/ostree/prepare-root.conf << 'EOF' + [composefs] + enabled = yes + EOF diff --git a/config.ini b/config.ini index fedaedd..a91c1c6 100644 --- a/config.ini +++ b/config.ini @@ -10,7 +10,7 @@ os_pretty_name = Fedora IoT tree_name = docker-host tree_file = %(os_name)s-%(tree_name)s.json arch = x86_64 -release = f40 +release = f46 ref = %(os_name)s/rawhide/%(arch)s/%(tree_name)s yum_baseurl = http://dl.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/%(arch)s/os/ # lorax_additional_repos = http://127.0.0.1/fedora-iot/local-overrides diff --git a/fedora-40.repo b/fedora-46.repo similarity index 60% rename from fedora-40.repo rename to fedora-46.repo index 7a3c762..03af70c 100644 --- a/fedora-40.repo +++ b/fedora-46.repo @@ -1,12 +1,12 @@ -[fedora-40] -name=Fedora 40 - $basearch +[fedora-46] +name=Fedora 46 - $basearch failovermethod=priority -#baseurl=http://download.fedoraproject.org/pub/fedora/linux/releases/40/Everything/$basearch/os/ -metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-40&arch=$basearch +#baseurl=http://download.fedoraproject.org/pub/fedora/linux/releases/46/Everything/$basearch/os/ +metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-46&arch=$basearch enabled=1 #metadata_expire=7d repo_gpgcheck=0 type=rpm gpgcheck=1 -gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-40-$basearch +gpgkey=file:///usr/share/pki/rpm-gpg/RPM-GPG-KEY-fedora-46-$basearch skip_if_unavailable=False diff --git a/fedora-iot-base.yaml b/fedora-iot-base.yaml index 2c98c56..79160b5 100644 --- a/fedora-iot-base.yaml +++ b/fedora-iot-base.yaml @@ -1,12 +1,17 @@ +include: + - bootupd.yaml + # Enable composefs + - composefs.yaml + # Read only sysroot + - sysroot-ro.yaml ref: fedora/rawhide/${basearch}/iot repos: [] selinux: true -ima: true tmp-is-dir: true recommends: false documentation: false -automatic_version_prefix: '40' -mutate-os-release: '40' +automatic_version_prefix: '46' +mutate-os-release: '46' initramfs-args: - "--no-hostonly" - "--add" @@ -69,14 +74,18 @@ check-groups: type: file filename: group packages: +# This list of packages should be kept in sync with +# the iot-commit definition is osbuild, as found here: +# https://github.com/osbuild/images/blob/0584c20b0c14a89e833b875252114ceff61805e8/pkg/distro/fedora/package_sets.go#L123 - fedora-release-iot +- fedora-iot-config - aardvark-dns - atheros-firmware - attr - authselect -- basesystem - bash - bash-completion +- bootc - brcmfmac-firmware - chrony - clevis @@ -91,14 +100,16 @@ packages: - criu-libs - cryptsetup - curl -- dbus-parsec -- dnsmasq +- dnf5 +- dnf5-plugins - dosfstools - dracut-config-generic - dracut-network - e2fsprogs - efibootmgr - fdo-client +- fdo-owner-cli +- filesystem - firewalld - fwupd - fwupd-efi @@ -111,15 +122,15 @@ packages: - greenboot-default-health-checks - gzip - hostname -- ignition +- ignition-edge - ima-evm-utils - iproute - iputils - iwd - iwlwifi-mvm-firmware - kernel -- kernel-tools - keyutils +- kmscon - less - libsss_sudo - linux-firmware @@ -132,12 +143,11 @@ packages: - openssl - openssh-clients - openssh-server -- parsec +- passt +- passt-selinux - pinentry - podman -- podman-plugins - policycoreutils -- policycoreutils-python-utils - polkit - procps-ng - realtek-firmware @@ -153,6 +163,7 @@ packages: - sssd-client - sudo - systemd +- systemd-pam - systemd-resolved - tar - tmux @@ -165,7 +176,6 @@ packages: - wireless-regdb - xfsprogs - xz -- zezere-ignition - zram-generator - zram-generator-defaults packages-aarch64: @@ -182,16 +192,14 @@ packages-x86_64: default_target: multi-user.target units: - NetworkManager.service +- fdo-client-linuxapp.service - fedora-iot-config-remote-fix.service - firewalld.service - sshd.service +- systemd-firstboot.service - greenboot-grub2-set-counter.service - greenboot-grub2-set-success.service - greenboot-healthcheck.service - greenboot-rpm-ostree-grub2-check-fallback.service - greenboot-status.service - greenboot-task-runner.service -- parsec.service -- dbus-parsec.service -- zezere_ignition.timer -- zezere_ignition_banner.service diff --git a/fedora-iot-bootc.yaml b/fedora-iot-bootc.yaml new file mode 100644 index 0000000..6ae5882 --- /dev/null +++ b/fedora-iot-bootc.yaml @@ -0,0 +1,15 @@ +releasever: rawhide +variables: + distro: "fedora-iot" + +repos: + - fedora-rawhide + +metadata: + name: fedora-iot-bootc-base + summary: Fedora IoT bootc base image + +include: + - fedora-bootc-base/manifest.yaml + - fedora-bootc-base/kernel.yaml + diff --git a/fedora-iot.yaml b/fedora-iot.yaml index b83ea21..b770dff 100644 --- a/fedora-iot.yaml +++ b/fedora-iot.yaml @@ -1,4 +1,5 @@ include: fedora-iot-base.yaml ref: fedora/rawhide/${basearch}/iot + repos: -- fedora-40 +- fedora-46 diff --git a/fedora-rawhide.repo b/fedora-rawhide.repo new file mode 100644 index 0000000..30b51c9 --- /dev/null +++ b/fedora-rawhide.repo @@ -0,0 +1,6 @@ +[fedora-rawhide] +name=Fedora rawhide $basearch +mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=rawhide&arch=$basearch +enabled=1 +gpgcheck=1 +metadata_expire=1d diff --git a/justfile b/justfile new file mode 100644 index 0000000..3eaf256 --- /dev/null +++ b/justfile @@ -0,0 +1,156 @@ +# This is a justfile. See https://github.com/casey/just +# This is only used for local development. The builds made on the Fedora +# infrastructure are run via Pungi in a Koji runroot. + +# Set a default for some recipes +default_variant := "fedora-iot" +default_arch := "default" +# Current default in Pungi +force_nocache := "true" + +# Just doesn't have a native dict type, but quoted bash dictionary works fine +pretty_names := '( + [fedora-iot]="IoT" +)' + +# Default is to only validate the manifests +all: validate + +# Basic validation to make sure the manifests are not completely broken +validate: + ./ci/validate + +# Output the processed manifest for a given variant (defaults to Silverblue) +manifest variant=default_variant: + #!/bin/bash + set -euo pipefail + + rpm-ostree compose tree --print-only --repo=repo {{variant}}.yaml + +# Perform dependency resolution for a given variant (defaults to Silverblue) +compose-dry-run variant=default_variant: + #!/bin/bash + set -euxo pipefail + + mkdir -p repo cache logs + if [[ ! -f "repo/config" ]]; then + pushd repo > /dev/null || exit 1 + ostree init --repo . --mode=bare-user + popd > /dev/null || exit 1 + fi + + rpm-ostree compose tree --unified-core --repo=repo --dry-run {{variant}}.yaml + +# Alias/shortcut for compose-image command +compose variant=default_variant: (compose-image variant) + +# Compose a variant using the legacy non container path (defaults to Silverblue) +compose-legacy variant=default_variant: + #!/bin/bash + set -euxo pipefail + + declare -A pretty_names={{pretty_names}} + variant={{variant}} + variant_pretty=${pretty_names[$variant]-} + if [[ -z $variant_pretty ]]; then + echo "Unknown variant" + exit 1 + fi + + ./ci/validate > /dev/null || (echo "Failed manifest validation" && exit 1) + + mkdir -p repo cache logs + if [[ ! -f "repo/config" ]]; then + pushd repo > /dev/null || exit 1 + ostree init --repo . --mode=bare-user + popd > /dev/null || exit 1 + fi + # Set option to reduce fsync for transient builds + ostree --repo=repo config set 'core.fsync' 'false' + + buildid="$(date '+%Y%m%d.0')" + timestamp="$(date --iso-8601=sec)" + echo "${buildid}" > .buildid + + version="$(rpm-ostree compose tree --print-only --repo=repo ${variant}.yaml | jq -r '."mutate-os-release"')" + echo "Composing ${variant_pretty} ${version}.${buildid} ..." + + ARGS="--repo=repo --cachedir=cache" + ARGS+=" --unified-core" + if [[ {{force_nocache}} == "true" ]]; then + ARGS+=" --force-nocache" + fi + CMD="rpm-ostree" + if [[ ${EUID} -ne 0 ]]; then + CMD="sudo rpm-ostree" + fi + + ${CMD} compose tree ${ARGS} \ + --add-metadata-string="version=${variant_pretty} ${version}.${buildid}" \ + "${variant}.yaml" \ + |& tee "logs/${variant}_${version}_${buildid}.${timestamp}.log" + + if [[ ${EUID} -ne 0 ]]; then + sudo chown --recursive "$(id --user --name):$(id --group --name)" repo cache + fi + + ostree summary --repo=repo --update + +# Compose an Ostree Native Container OCI image +compose-image variant=default_variant: + #!/bin/bash + set -euxo pipefail + + declare -A pretty_names={{pretty_names}} + variant={{variant}} + variant_pretty=${pretty_names[$variant]-} + if [[ -z $variant_pretty ]]; then + echo "Unknown variant" + exit 1 + fi + + ./ci/validate > /dev/null || (echo "Failed manifest validation" && exit 1) + + mkdir -p repo cache + if [[ ! -f "repo/config" ]]; then + pushd repo > /dev/null || exit 1 + ostree init --repo . --mode=bare-user + popd > /dev/null || exit 1 + fi + # Set option to reduce fsync for transient builds + ostree --repo=repo config set 'core.fsync' 'false' + + buildid="$(date '+%Y%m%d.0')" + timestamp="$(date --iso-8601=sec)" + echo "${buildid}" > .buildid + + version="$(rpm-ostree compose tree --print-only --repo=repo ${variant}.yaml | jq -r '."mutate-os-release"')" + echo "Composing ${variant_pretty} ${version}.${buildid} ..." + + ARGS="--cachedir=cache --initialize" + if [[ {{force_nocache}} == "true" ]]; then + ARGS+=" --force-nocache" + fi + # To debug with gdb, use: gdb --args ... + CMD="rpm-ostree" + if [[ ${EUID} -ne 0 ]]; then + CMD="sudo rpm-ostree" + fi + + ${CMD} compose image ${ARGS} \ + --label="quay.expires-after=4w" \ + "${variant}.yaml" \ + "${variant}.ociarchive" + +# Clean up everything +clean-all: + just clean-repo + just clean-cache + +# Only clean the ostree repo +clean-repo: + rm -rf ./repo + +# Only clean the package and repo caches +clean-cache: + rm -rf ./cache diff --git a/sysroot-ro.yaml b/sysroot-ro.yaml new file mode 100644 index 0000000..8214f05 --- /dev/null +++ b/sysroot-ro.yaml @@ -0,0 +1,15 @@ +# Set up default root config to mount sysroot as read only +# https://fedoraproject.org/wiki/Changes/Silverblue_Kinoite_readonly_sysroot +# See: https://gitlab.com/fedora/bootc/base-images/-/blob/main/tier-0/ostree.yaml +# See: https://github.com/fedora-iot/iot-distro/issues/81 + +postprocess: + - | + #!/usr/bin/env bash + set -xeuo pipefail + + install -dm 0755 -o 0 -g 0 /usr/lib/ostree + cat >> /usr/lib/ostree/prepare-root.conf << 'EOF' + [sysroot] + readonly = true + EOF diff --git a/treecompose-post.sh b/treecompose-post.sh index a47d8cf..e62b942 100755 --- a/treecompose-post.sh +++ b/treecompose-post.sh @@ -18,7 +18,7 @@ done # Remove loader directory causing issues in Anaconda in unified core mode # Will be obsolete once we start using bootupd -# See - https://pagure.io/workstation-ostree-config/pull-request/344 +# See - https://forge.fedoraproject.org/atomic-desktops/config/pulls/344 rm -rf /usr/lib/ostree-boot/loader # Undo RPM scripts enabling units; we want the presets to be canonical