diff --git a/bootc/Containerfile b/bootc/Containerfile index 437144d..5368683 100644 --- a/bootc/Containerfile +++ b/bootc/Containerfile @@ -1,7 +1,8 @@ -FROM quay.io/redhat-et/rhel-bootc-tegra:base +FROM registry.redhat.io/rhel9/rhel-bootc:9.6 ARG ADMIN_USERNAME=demo \ ADMIN_PASSWORD=redhat \ + NVIDIA_KERNEL_VERSION=5.14.0-427.22.1.el9_4 \ ENABLE_DNF_CACHE=1 \ LOCAL_RPM_REPO=0 @@ -18,12 +19,30 @@ if [[ "$LOCAL_RPM_REPO" == "1" ]]; then echo -e "[main]\nenabled=0" > /etc/dnf/plugins/subscription-manager.conf fi +if [ -n "$NVIDIA_KERNEL_VERSION" ]; then + echo "Replacing current kernel with a version compatible with the kernel modules shipped by Nvidia" + mkdir -p /tmp/rpms + dnf download -y --destdir /tmp/rpms kernel{,-core,-modules,-modules-core}-$NVIDIA_KERNEL_VERSION + rpm-ostree override replace /tmp/rpms/*.rpm + rm -rf /tmp/rpms +fi + if [[ "$LOCAL_RPM_REPO" != "1" ]]; then - #dnf config-manager --enable codeready-builder-for-rhel-9-$(arch)-rpms + dnf config-manager --enable codeready-builder-for-rhel-9-$(arch)-rpms dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm fi -dnf install -y mkpasswd NetworkManager-wifi podman skopeo git +dnf install -y mkpasswd NetworkManager-wifi podman skopeo git mosquitto + +if [[ "$(arch)" == "aarch64" ]]; then + echo "Installing the Nvidia stuff..." ; \ + if [[ "$LOCAL_RPM_REPO" != "1" ]]; then + curl -sSfL -o /etc/yum.repos.d/nvidia-l4t.repo https://repo.download.nvidia.com/jetson/rhel-9.4/r36.3.1/nvidia-l4t.repo + curl -sSfL -o /etc/yum.repos.d/nvidia-container-toolkit.repo https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo + dnf config-manager --enable nvidia-container-toolkit-experimental + fi + dnf install -y nvidia-jetpack-kmod nvidia-jetpack-all nvidia-container-toolkit-base +fi if [ -n "$ADMIN_USERNAME" ]; then useradd -m -G wheel -p "$(echo -n "$ADMIN_PASSWORD" | mkpasswd -m bcrypt --stdin)" "$ADMIN_USERNAME" diff --git a/bootc/Containerfile.rh b/bootc/Containerfile.rh new file mode 100644 index 0000000..1f05f57 --- /dev/null +++ b/bootc/Containerfile.rh @@ -0,0 +1,37 @@ +FROM quay.io/redhat-et/rhel-bootc-tegra:base + +ARG ADMIN_USERNAME=demo \ + ADMIN_PASSWORD=redhat \ + ENABLE_DNF_CACHE=1 \ + LOCAL_RPM_REPO=0 + +RUN < /etc/dnf/plugins/subscription-manager.conf +fi + +if [[ "$LOCAL_RPM_REPO" != "1" ]]; then + #dnf config-manager --enable codeready-builder-for-rhel-9-$(arch)-rpms + dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm +fi + +dnf install -y mkpasswd NetworkManager-wifi podman skopeo git + +if [ -n "$ADMIN_USERNAME" ]; then + useradd -m -G wheel -p "$(echo -n "$ADMIN_PASSWORD" | mkpasswd -m bcrypt --stdin)" "$ADMIN_USERNAME" +fi +EOF + +ADD --chown=root:root root / + +RUN set -Eeuo pipefail ; \ + systemctl enable nvidia-ctk-init.service ; \ + systemctl enable git-repo.service diff --git a/bootc/Containerfile.vanilla b/bootc/Containerfile.vanilla deleted file mode 100644 index 408d4b9..0000000 --- a/bootc/Containerfile.vanilla +++ /dev/null @@ -1,55 +0,0 @@ -FROM registry.redhat.io/rhel9/rhel-bootc:9.4 - -ARG ADMIN_USERNAME=demo \ - ADMIN_PASSWORD=redhat \ - NVIDIA_KERNEL_VERSION=5.14.0-427.22.1.el9_4 \ - ENABLE_DNF_CACHE=1 \ - LOCAL_RPM_REPO=0 - -RUN < /etc/dnf/plugins/subscription-manager.conf -fi - -if [ -n "$NVIDIA_KERNEL_VERSION" ]; then - echo "Replacing current kernel with a version compatible with the kernel modules shipped by Nvidia" - mkdir -p /tmp/rpms - dnf download -y --destdir /tmp/rpms kernel{,-core,-modules,-modules-core}-$NVIDIA_KERNEL_VERSION - rpm-ostree override replace /tmp/rpms/*.rpm - rm -rf /tmp/rpms -fi - -if [[ "$LOCAL_RPM_REPO" != "1" ]]; then - dnf config-manager --enable codeready-builder-for-rhel-9-$(arch)-rpms - dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm -fi - -dnf install -y mkpasswd NetworkManager-wifi podman skopeo git -if [[ "$(arch)" == "aarch64" ]]; then - echo "Installing the Nvidia stuff..." ; \ - if [[ "$LOCAL_RPM_REPO" != "1" ]]; then - curl -sSfL -o /etc/yum.repos.d/nvidia-l4t.repo https://repo.download.nvidia.com/jetson/rhel-9.4/r36.3.1/nvidia-l4t.repo - curl -sSfL -o /etc/yum.repos.d/nvidia-container-toolkit.repo https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo - dnf config-manager --enable nvidia-container-toolkit-experimental - fi - dnf install -y nvidia-jetpack-kmod nvidia-jetpack-all nvidia-container-toolkit-base -fi - -if [ -n "$ADMIN_USERNAME" ]; then - useradd -m -G wheel -p "$(echo -n "$ADMIN_PASSWORD" | mkpasswd -m bcrypt --stdin)" "$ADMIN_USERNAME" -fi -EOF - -ADD --chown=root:root root / - -RUN set -Eeuo pipefail ; \ - systemctl enable nvidia-ctk-init.service ; \ - systemctl enable git-repo.service diff --git a/bootc/artefacts-iso.sh b/bootc/artefacts-iso.sh index e54c769..a233bfa 100755 --- a/bootc/artefacts-iso.sh +++ b/bootc/artefacts-iso.sh @@ -23,11 +23,10 @@ declare -A ARCH_KERNEL_CMDLINE=(["aarch64"]="console=ttyTCU0 console=tty0" ["x86 # $(base32 -w 80 auth.json) # EOB32 -declare -a KICKSTART_ADDITIONAL_FILES_TAR_CMD_PRE=( ) -declare -a KICKSTART_ADDITIONAL_FILES_TAR_CMD_POST=( tar -zc -C post --owner=root --group=root . ) declare -A KICKSTART_SNIPPETS=() KICKSTART_SNIPPETS+=( ["restart networkmanager"]=" systemctl restart --no-block NetworkManager.service +/usr/bin/nm-online -s -q " ) KICKSTART_SNIPPETS+=( ["bootc switch"]=" bootc switch --mutate-in-place --transport registry $TARGET_IMAGE @@ -41,7 +40,8 @@ chmod 600 /etc/ostree/auth.json KICKSTART_SNIPPETS+=( ["/etc/containers/registries.conf.d/bootc-registry.conf"]=" cat > /etc/containers/registries.conf.d/bootc-registry.conf <<\"EOF\" [[registry]] -location=\"${TARGET_IMAGE%%/*}\" +prefix=\"${TARGET_IMAGE%%/*}\" +location=\"${TARGET_IMAGE%%/*}:80\" insecure=true EOF " ) diff --git a/bootc/artefacts-qcow2.sh b/bootc/artefacts-qcow2.sh index f5cc70f..3f377f7 100755 --- a/bootc/artefacts-qcow2.sh +++ b/bootc/artefacts-qcow2.sh @@ -19,7 +19,7 @@ function bootc_image_builder () { -v "$config_file:/config.toml:ro" -v "$output:/output" -v "$rpmmd:/rpmmd" -v "$store:/store" \ -v /var/lib/containers/storage:/var/lib/containers/storage \ -v "$REGISTRY_AUTH_FILE:/auth.json:ro" -e "REGISTRY_AUTH_FILE=/auth.json" \ - "$BOOTC_IMAGE:$RHEL_VERSION" --type "$type" --config /config.toml "$image" "${args[@]}" + "$BOOTC_IMAGE:$BOOTC_VERSION" --type "$type" --config /config.toml "$image" "${args[@]}" } arch="$(arch)" diff --git a/bootc/build.sh b/bootc/build.sh index d3ab6e1..7b47105 100755 --- a/bootc/build.sh +++ b/bootc/build.sh @@ -9,7 +9,6 @@ source ./env.sh ./common.sh # Build images -declare -A PODMAN_ARCH_OPTS=(["aarch64"]="--platform linux/arm64/v8" ["x86_64"]="--platform linux/amd64") for arch in "${ARCHITECTURES[@]}"; do echo "Building $NAME image for $arch..." rm -rf etc diff --git a/bootc/common.sh b/bootc/common.sh index 81b5d38..e298c0a 100755 --- a/bootc/common.sh +++ b/bootc/common.sh @@ -1,5 +1,12 @@ #!/bin/sh +set -Eeuo pipefail + +if [[ "$UID" -ne 0 ]]; then + echo "This command must be run as root!" + exit 1 +fi + # Source variables and environment source ./env.sh @@ -14,24 +21,20 @@ if [ ! -f "$REGISTRY_AUTH_FILE" ]; then podman login registry.redhat.io echo "Logging in quay.io registry" podman login quay.io - echo "Done" + echo "Done" read -p "Press enter to continue " fi # Pull echo "Pulling images..." -if ! podman image exists $BOOTC_IMAGE:$RHEL_VERSION; then - podman pull $BOOTC_IMAGE:$RHEL_VERSION -fi -if ! podman image exists $RHEL_IMAGE-x86_64:$RHEL_VERSION; then - podman rmi -i $RHEL_IMAGE:$RHEL_VERSION - podman pull --platform linux/amd64 $RHEL_IMAGE:$RHEL_VERSION - podman tag $RHEL_IMAGE:$RHEL_VERSION $RHEL_IMAGE-x86_64:$RHEL_VERSION - podman rmi $RHEL_IMAGE:$RHEL_VERSION -fi -if ! podman image exists $RHEL_IMAGE-aarch64:$RHEL_VERSION; then - podman rmi -i $RHEL_IMAGE:$RHEL_VERSION - podman pull --platform linux/arm64/v8 $RHEL_IMAGE:$RHEL_VERSION - podman tag $RHEL_IMAGE:$RHEL_VERSION $RHEL_IMAGE-aarch64:$RHEL_VERSION - podman rmi $RHEL_IMAGE:$RHEL_VERSION +if ! podman image exists $BOOTC_IMAGE:$BOOTC_VERSION; then + podman pull $BOOTC_IMAGE:$BOOTC_VERSION fi +for arch in "${ARCHITECTURES[@]}"; do + if ! podman image exists $RHEL_IMAGE-$arch:$RHEL_VERSION; then + podman rmi -i $RHEL_IMAGE:$RHEL_VERSION + podman pull ${PODMAN_ARCH_OPTS[$arch]} $RHEL_IMAGE:$RHEL_VERSION + podman tag $RHEL_IMAGE:$RHEL_VERSION $RHEL_IMAGE-$arch:$RHEL_VERSION + podman rmi $RHEL_IMAGE:$RHEL_VERSION + fi +done diff --git a/bootc/env.sh b/bootc/env.sh index 7eb0851..0a46325 100644 --- a/bootc/env.sh +++ b/bootc/env.sh @@ -2,14 +2,17 @@ ### General ### PODMAN_NETWORK="edge-ai" -RHEL_VERSION="9.4" +RHEL_VERSION="9.6" RHEL_IMAGE="registry.redhat.io/rhel9/rhel-bootc" -BOOTC_IMAGE="registry.redhat.io/rhel9/bootc-image-builder" +BOOTC_IMAGE="registry.redhat.io/rhel10/bootc-image-builder" +BOOTC_VERSION="10.0" NAME="bootc-edge-ai" TARGET_IMAGE="quay.io/nmasse-redhat/$NAME:latest" # All architectures to build for declare -a ARCHITECTURES=("x86_64" "aarch64") +#declare -a ARCHITECTURES=("aarch64") +declare -A PODMAN_ARCH_OPTS=(["aarch64"]="--platform linux/arm64/v8" ["x86_64"]="--platform linux/amd64") ### ### Environment Variables diff --git a/tekton/README.md b/tekton/README.md index 32c4dcb..d6b9897 100644 --- a/tekton/README.md +++ b/tekton/README.md @@ -52,6 +52,14 @@ EOF oc create secret generic github-authentication --from-literal=.git-credentials=https://user:password@github.com --from-file=.gitconfig=gitconfig ``` +## Authentication to MQTT + +Set the tekton password in the mosquitto passwd file (**common/mosquitto.conf**) and then: + +```sh +oc create secret generic mqtt-config --from-literal=OTA_MQTT_URL=mqtts://tekton:secret@mosquitto-build-multiarch.apps.nmasse-q2-2025.sandbox1038.opentlc.com:443/bootc/updates +``` + ## Rclone config for AWS S3 **rclone.conf**: diff --git a/tekton/common/kustomization.yaml b/tekton/common/kustomization.yaml index d5d0386..92a08e2 100644 --- a/tekton/common/kustomization.yaml +++ b/tekton/common/kustomization.yaml @@ -1,5 +1,7 @@ resources: - serviceaccount-buildbot.yaml +- mosquitto.yaml +- task-ota-update.yaml - task-buildah.yaml - task-git-clone.yaml - task-rclone.yaml diff --git a/tekton/common/mosquitto.yaml b/tekton/common/mosquitto.yaml new file mode 100644 index 0000000..8c27396 --- /dev/null +++ b/tekton/common/mosquitto.yaml @@ -0,0 +1,172 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mosquitto-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + volumeMode: Filesystem +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: mosquitto +spec: + replicas: 1 + serviceName: mosquitto + selector: + matchLabels: + name: mosquitto + template: + metadata: + labels: + name: mosquitto + spec: + containers: + - name: mosquitto + image: docker.io/library/eclipse-mosquitto:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8883 + livenessProbe: + tcpSocket: + port: 1883 + failureThreshold: 1 + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - mosquitto_pub + - -t + - _ping + - -m + - ping + failureThreshold: 1 + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 + volumeMounts: + - name: data + mountPath: /mosquitto/data + subPath: data + - name: data + mountPath: /mosquitto/log + subPath: log + - name: config + mountPath: /mosquitto/config + - name: tls + mountPath: /mosquitto/tls + readOnly: true + - name: ca + mountPath: /mosquitto/ca + readOnly: true + - name: mosquitto-subscriber + image: docker.io/library/eclipse-mosquitto:latest + imagePullPolicy: IfNotPresent + command: + - mosquitto_sub + args: + - -v + - -t + - '#' + volumeMounts: + - name: tls + mountPath: /mosquitto/tls + readOnly: true + - name: ca + mountPath: /mosquitto/ca + readOnly: true + terminationGracePeriodSeconds: 30 + volumes: + - name: data + persistentVolumeClaim: + claimName: mosquitto-data + - name: config + configMap: + name: mosquitto-config + defaultMode: 0640 + - name: ca + configMap: + name: openshift-service-ca.crt + - name: tls + secret: + secretName: mosquitto-tls +--- +apiVersion: v1 +kind: Service +metadata: + name: mosquitto + annotations: + service.beta.openshift.io/serving-cert-secret-name: mosquitto-tls +spec: + type: ClusterIP + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: mqtt + port: 1883 + protocol: TCP + targetPort: 1883 + - name: tls + port: 8883 + protocol: TCP + targetPort: 8883 + selector: + name: mosquitto + sessionAffinity: None +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mosquitto-config +data: + mosquitto.conf: | + autosave_interval 60 + persistence true + persistence_file mosquitto.db + persistence_location /mosquitto/data + allow_anonymous true + password_file /mosquitto/config/pwfile + acl_file /mosquitto/config/aclfile + listener 1883 0.0.0.0 + protocol mqtt + listener 8883 0.0.0.0 + protocol mqtt + cafile /mosquitto/ca/service-ca.crt + certfile /mosquitto/tls/tls.crt + keyfile /mosquitto/tls/tls.key + aclfile: | + # This affects access control for clients with no username. + topic read $SYS/# + # Allow anonymous users to read all updates. + topic read # + # Allow the tekton user to write updates. + user tekton + topic readwrite # + # This affects all clients. + pattern write /broker/connection/%c/state + # pwfile is generated using "mosquitto_passwd -c /tmp/pwfile $username" + pwfile: | + tekton:REDACTED +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: mosquitto +spec: + to: + kind: Service + name: mosquitto + port: + targetPort: 8883 + tls: + termination: passthrough + insecureEdgeTerminationPolicy: None diff --git a/tekton/common/storage.yaml b/tekton/common/storage.yaml index 48ac429..6139e6f 100644 --- a/tekton/common/storage.yaml +++ b/tekton/common/storage.yaml @@ -23,3 +23,16 @@ spec: storage: 1Gi volumeMode: Filesystem storageClassName: efs-csi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: bootc-rpms +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Gi + volumeMode: Filesystem + storageClassName: efs-csi diff --git a/tekton/common/task-buildah.yaml b/tekton/common/task-buildah.yaml index 491df88..1475902 100644 --- a/tekton/common/task-buildah.yaml +++ b/tekton/common/task-buildah.yaml @@ -23,6 +23,9 @@ spec: - name: pypi-mirror-url type: string default: "" + results: + - name: image-digest + description: The digest of the built image workspaces: - name: source-workspace description: Workspace containing source code @@ -140,7 +143,7 @@ spec: # Push Manifest echo "Pushing to $TARGET_IMAGE..." - buildah manifest push localhost/image docker://$TARGET_IMAGE + buildah manifest push "--digestfile=$(results.image-digest.path)" localhost/image docker://$TARGET_IMAGE securityContext: ## Buildah needs privileges to use the "overlay" Storage Driver. privileged: true diff --git a/tekton/common/task-ota-update.yaml b/tekton/common/task-ota-update.yaml new file mode 100644 index 0000000..2d8c7a5 --- /dev/null +++ b/tekton/common/task-ota-update.yaml @@ -0,0 +1,24 @@ +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: ota-update +spec: + params: + - name: otaVersion + type: string + steps: + - name: ota-update + image: docker.io/library/eclipse-mosquitto:latest + env: + - name: OTA_MQTT_URL + valueFrom: + secretKeyRef: + name: "mqtt-config" + key: "OTA_MQTT_URL" + - name: OTA_VERSION + value: "$(params.otaVersion)" + script: | + #!/bin/bash + set -Eeuo pipefail + echo "Sending the OTA firmware udate notification for version $OTA_VERSION on $OTA_MQTT_TOPIC..." + mosquitto_pub -L "$OTA_MQTT_URL" -m "$OTA_VERSION" -d diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index d017332..7c2ec3a 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -118,3 +118,11 @@ spec: workspace: caches - name: entitlements workspace: entitlements + + - name: ota-update + runAfter: ["buildah-bootc"] + taskRef: + name: ota-update + params: + - name: otaVersion + value: $(tasks.buildah-bootc.results.image-digest)