diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..92c44a8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..5db72dd --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a8d7325 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,182 @@ +--- +name: Build Custom Image +on: + pull_request: + branches: + - main + schedule: + - cron: '05 10 * * *' # 10:05am UTC everyday + push: + branches: + - main + paths-ignore: + - '**/README.md' + workflow_dispatch: + +env: + IMAGE_NAME: "${{ github.event.repository.name }}" # the name of the image produced by this build, matches repo names + IMAGE_DESC: "My Customized Universal Blue Image" + IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit + ARTIFACTHUB_LOGO_URL: "https://avatars.githubusercontent.com/u/120078124?s=200&v=4" # You should put your own image here so that you get a fancy profile image on https://artifacthub.io/! + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }}-${{ inputs.brand_name}}-${{ inputs.stream_name }} + cancel-in-progress: true + +jobs: + build_push: + name: Build and push image + runs-on: ubuntu-24.04 + + permissions: + contents: read + packages: write + id-token: write + + steps: + # These stage versions are pinned by https://github.com/renovatebot/renovate + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + # This is optional, but if you see that your builds are way too big for the runners, you can enable this by uncommenting the following lines: + # - name: Maximize build space + # uses: ublue-os/remove-unwanted-software@517622d6452028f266b7ba4cc9a123b5f58a6b53 # v7 + # with: + # remove-codeql: true + + - name: Get current date + id: date + run: | + # This generates a timestamp like what is defined on the ArtifactHub documentation + # E.G: 2022-02-08T15:38:15Z' + # https://artifacthub.io/docs/topics/repositories/container-images/ + # https://linux.die.net/man/1/date + echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" >> $GITHUB_OUTPUT + + # Image metadata for https://artifacthub.io/ - This is optional but is highly recommended so we all can get a index of all the custom images + # The metadata by itself is not going to do anything, you choose if you want your image to be on ArtifactHub or not. + - name: Image Metadata + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5 + id: metadata + with: + # This generates all the tags for your image, you can add custom tags here too! + # By default, it should generate "latest" and "latest.(date here)". + tags: | + type=raw,value=latest + type=raw,value=latest.{{date 'YYYYMMDD'}} + type=raw,value={{date 'YYYYMMDD'}} + type=sha,enable=${{ github.event_name == 'pull_request' }} + type=ref,event=pr + labels: | + io.artifacthub.package.readme-url=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/refs/heads/main/README.md + org.opencontainers.image.created=${{ steps.date.outputs.date }} + org.opencontainers.image.description=${{ env.IMAGE_DESC }} + org.opencontainers.image.documentation=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/refs/heads/main/README.md + org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/blob/main/Containerfile + org.opencontainers.image.title=${{ env.IMAGE_NAME }} + org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + org.opencontainers.image.vendor=${{ github.repository_owner }} + org.opencontainers.image.version=latest + io.artifacthub.package.deprecated=false + io.artifacthub.package.keywords=bootc,ublue,universal-blue + io.artifacthub.package.license=Apache-2.0 + io.artifacthub.package.logo-url=${{ env.ARTIFACTHUB_LOGO_URL }} + io.artifacthub.package.prerelease=false + containers.bootc=1 + sep-tags: " " + sep-annotations: " " + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2 + with: + containerfiles: | + ./Containerfile + # Postfix image name with -custom to make it a little more descriptive + # Syntax: https://docs.github.com/en/actions/learn-github-actions/expressions#format + image: ${{ env.IMAGE_NAME }} + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + oci: false + + # Rechunk is a script that we use on Universal Blue to make sure there isnt a single huge layer when your image gets published. + # This does not make your image faster to download, just provides better resumability and fixes a few errors. + # Documentation for Rechunk is provided on their github repository at https://github.com/hhd-dev/rechunk + # You can enable it by uncommenting the following lines: + # - name: Run Rechunker + # id: rechunk + # uses: hhd-dev/rechunk@f153348d8100c1f504dec435460a0d7baf11a9d2 # v1.1.1 + # with: + # rechunk: 'ghcr.io/hhd-dev/rechunk:v1.0.1' + # ref: "localhost/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" + # prev-ref: "${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" + # skip_compression: true + # version: ${{ env.CENTOS_VERSION }} + # labels: ${{ steps.metadata.outputs.labels }} # Rechunk strips out all the labels during build, this needs to be reapplied here with newline separator + + # This is necessary so that the podman socket can find the rechunked image on its storage + # - name: Load in podman and tag + # run: | + # IMAGE=$(podman pull ${{ steps.rechunk.outputs.ref }}) + # sudo rm -rf ${{ steps.rechunk.outputs.output }} + # for tag in ${{ steps.metadata.outputs.tags }}; do + # podman tag $IMAGE ${{ env.IMAGE_NAME }}:$tag + # done + + # These `if` statements are so that pull requests for your custom images do not make it publish any packages under your name without you knowing + # They also check if the runner is on the default branch so that things like the merge queue (if you enable it), are going to work + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3 + if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Workaround bug where capital letters in your GitHub username make it impossible to push to GHCR. + # https://github.com/macbre/push-to-ghcr/issues/12 + - name: Lowercase Registry + id: registry_case + uses: ASzc/change-string-case-action@v6 + with: + string: ${{ env.IMAGE_REGISTRY }} + + - name: Lowercase Image + id: image_case + uses: ASzc/change-string-case-action@v6 + with: + string: ${{ env.IMAGE_NAME }} + + - name: Push To GHCR + uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 + if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + id: push + env: + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ github.token }} + with: + registry: ${{ steps.registry_case.outputs.lowercase }} + image: ${{ steps.image_case.outputs.lowercase }} + tags: ${{ steps.metadata.outputs.tags }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + # This section is optional and only needs to be enabled if you plan on distributing + # your project for others to consume. You will need to create a public and private key + # using Cosign and save the private key as a repository secret in Github for this workflow + # to consume. For more details, review the image signing section of the README. + - name: Install Cosign + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 + if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + + - name: Sign container image + if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + run: | + IMAGE_FULL="${{ steps.registry_case.outputs.lowercase }}/${{ steps.image_case.outputs.lowercase }}" + for tag in ${{ steps.metadata.outputs.tags }}; do + cosign sign -y --key env://COSIGN_PRIVATE_KEY $IMAGE_FULL:$tag + done + env: + TAGS: ${{ steps.push.outputs.digest }} + COSIGN_EXPERIMENTAL: false + COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbc6166 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cosign.key +_build_* diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..67bd49c --- /dev/null +++ b/Containerfile @@ -0,0 +1,21 @@ +FROM ghcr.io/ublue-os/silverblue-main:latest + +## Other possible base images include: +# FROM ghcr.io/ublue-os/bazzite:stable +# FROM ghcr.io/ublue-os/bluefin-nvidia:stable +# +# ... and so on, here are more base images +# Universal Blue Images: https://github.com/orgs/ublue-os/packages +# Fedora base image: quay.io/fedora/fedora-bootc:41 +# CentOS base images: quay.io/centos-bootc/centos-bootc:stream10 + +### MODIFICATIONS +## make modifications desired in your image and install packages by modifying the build.sh script +## the following RUN directive does all the things required to run "build.sh" as recommended. + +COPY build.sh /tmp/build.sh + +RUN mkdir -p /var/lib/alternatives && \ + /tmp/build.sh && \ + ostree container commit + diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..d718b0a --- /dev/null +++ b/Justfile @@ -0,0 +1,204 @@ +export repo_organization := env("GITHUB_REPOSITORY_OWNER", "yourname") +export image_name := env("IMAGE_NAME", "yourimage") +export default_tag := env("DEFAULT_TAG", "latest") +export bib_image := env("BIB_IMAGE", "quay.io/centos-bootc/bootc-image-builder:latest") + +export SUDO_DISPLAY := if `if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then echo true; fi` == "true" { "true" } else { "false" } +export SUDOIF := if `id -u` == "0" { "" } else { if SUDO_DISPLAY == "true" { "sudo --askpass" } else { "sudo" } } +export PODMAN := if path_exists("/usr/bin/podman") == "true" { env("PODMAN", "/usr/bin/podman") } else { if path_exists("/usr/bin/docker") == "true" { env("PODMAN", "docker") } else { env("PODMAN", "exit 1 ; ") } } + +alias build-vm := build-qcow2 +alias rebuild-vm := rebuild-qcow2 +alias run-vm := run-vm-qcow2 + +[private] +default: + @just --list + +# Check Just Syntax +[group('Just')] +check: + #!/usr/bin/bash + find . -type f -name "*.just" | while read -r file; do + echo "Checking syntax: $file" + just --unstable --fmt --check -f $file + done + echo "Checking syntax: Justfile" + just --unstable --fmt --check -f Justfile + +# Fix Just Syntax +[group('Just')] +fix: + #!/usr/bin/bash + find . -type f -name "*.just" | while read -r file; do + echo "Checking syntax: $file" + just --unstable --fmt -f $file + done + echo "Checking syntax: Justfile" + just --unstable --fmt -f Justfile || { exit 1; } + +# Clean Repo +[group('Utility')] +clean: + #!/usr/bin/bash + set -eoux pipefail + touch _build + find *_build* -exec rm -rf {} \; + rm -f previous.manifest.json + rm -f changelog.md + rm -f output.env + +# Sudo Clean Repo +[group('Utility')] +[private] +sudo-clean: + ${SUDOIF} just clean + +build $target_image=image_name $tag=default_tag: + #!/usr/bin/env bash + + # Get Version + ver="${tag}-$(date +%Y%m%d)" + + BUILD_ARGS=() + BUILD_ARGS+=("--build-arg" "IMAGE_NAME=${image_name}") + BUILD_ARGS+=("--build-arg" "IMAGE_VENDOR=${repo_organization}") + if [[ -z "$(git status -s)" ]]; then + BUILD_ARGS+=("--build-arg" "SHA_HEAD_SHORT=$(git rev-parse --short HEAD)") + fi + + ${PODMAN} build \ + "${BUILD_ARGS[@]}" \ + --pull=newer \ + --tag "${image_name}:${tag}" \ + . + +_rootful_load_image $target_image=image_name $tag=default_tag: + #!/usr/bin/bash + set -eoux pipefail + + if [[ -n "${SUDO_USER:-}" || "${UID}" -eq "0" ]]; then + echo "Already root or running under sudo, no need to load image from user ${PODMAN}." + exit 0 + fi + + set +e + resolved_tag=$(${PODMAN} inspect -t image "${target_image}:${tag}" | jq -r '.[].RepoTags.[0]') + return_code=$? + set -e + + if [[ $return_code -eq 0 ]]; then + # Load into Rootful ${PODMAN} + ID=$(${SUDOIF} ${PODMAN} images --filter reference="${target_image}:${tag}" --format "'{{ '{{.ID}}' }}'") + if [[ -z "$ID" ]]; then + COPYTMP=$(mktemp -p "${PWD}" -d -t _build_podman_scp.XXXXXXXXXX) + ${SUDOIF} TMPDIR=${COPYTMP} ${PODMAN} image scp ${UID}@localhost::"${target_image}:${tag}" root@localhost::"${target_image}:${tag}" + rm -rf "${COPYTMP}" + fi + else + # Make sure the image is present and/or up to date + ${SUDOIF} ${PODMAN} pull "${target_image}:${tag}" + fi + +_build-bib $target_image $tag $type $config: (_rootful_load_image target_image tag) + #!/usr/bin/env bash + set -euo pipefail + + mkdir -p "output" + + echo "Cleaning up previous build" + if [[ $type == iso ]]; then + sudo rm -rf "output/bootiso" || true + else + sudo rm -rf "output/${type}" || true + fi + + args="--type ${type}" + + if [[ $target_image == localhost/* ]]; then + args+=" --local" + fi + + sudo ${PODMAN} run \ + --rm \ + -it \ + --privileged \ + --pull=newer \ + --net=host \ + --security-opt label=type:unconfined_t \ + -v $(pwd)/${config}:/config.toml:ro \ + -v $(pwd)/output:/output \ + -v /var/lib/containers/storage:/var/lib/containers/storage \ + "${bib_image}" \ + --rootfs btrfs \ + ${args} \ + "${target_image}" + + sudo chown -R $USER:$USER output + +_rebuild-bib $target_image $tag $type $config: (build target_image tag) && (_build-bib target_image tag type config) + +[group('Build Virtual Machine Image')] +build-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "qcow2" "image.toml") + +[group('Build Virtual Machine Image')] +build-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "raw" "image.toml") + +[group('Build Virtual Machine Image')] +build-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "iso" "iso.toml") + +[group('Build Virtual Machine Image')] +rebuild-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "qcow2" "image.toml") + +[group('Build Virtual Machine Image')] +rebuild-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "raw" "image.toml") + +[group('Build Virtual Machine Image')] +rebuild-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "iso" "iso.toml") + +_run-vm $target_image $tag $type $config: + #!/usr/bin/bash + set -eoux pipefail + + image_file="output/${type}/disk.${type}" + + if [[ $type == iso ]]; then + image_file="output/bootiso/install.iso" + fi + + if [[ ! -f "${image_file}" ]]; then + just "build-${type}" "$target_image" "$tag" + fi + + # Determine which port to use + port=8006; + while grep -q :${port} <<< $(ss -tunalp); do + port=$(( port + 1 )) + done + echo "Using Port: ${port}" + echo "Connect to http://localhost:${port}" + run_args=() + run_args+=(--rm --privileged) + run_args+=(--pull=newer) + run_args+=(--publish "127.0.0.1:${port}:8006") + run_args+=(--env "CPU_CORES=4") + run_args+=(--env "RAM_SIZE=8G") + run_args+=(--env "DISK_SIZE=64G") + # run_args+=(--env "BOOT_MODE=windows_secure") + run_args+=(--env "TPM=Y") + run_args+=(--env "GPU=Y") + run_args+=(--device=/dev/kvm) + run_args+=(--volume "${PWD}/${image_file}":"/boot.${type}") + run_args+=(docker.io/qemux/qemu-docker) + ${PODMAN} run "${run_args[@]}" & + xdg-open http://localhost:${port} + fg "%${PODMAN}" + +[group('Run Virtual Machine')] +run-vm-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "qcow2" "image-builder.config.toml") + +[group('Run Virtual Machine')] +run-vm-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "raw" "image-builder.config.toml") + +[group('Run Virtual Machine')] +run-vm-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "iso" "image-builder-iso.config.toml") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0cd1a5d --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# image-template + +# Purpose + +This repository is meant to be a template for building your own custom Universal Blue image. This template is the recommended way to make customizations to any image published by the Universal Blue Project: +- [Aurora](https://getaurora.dev/) +- [Bazzite](https://bazzite.gg/) +- [Bluefin](https://projectbluefin.io/) +- [uCore](https://projectucore.io/) +- [main](https://github.com/ublue-os/main/) +- [hwe](https://github.com/ublue-os/hwe/) + +or any other base image if you want to start from scratch: + +- Fedora: `quay.io/fedora/fedora-bootc:41` +- CentOS Stream 9: `quay.io/centos-bootc/centos-bootc:stream9` +- CentOS Stream 10 (in development): `quay.io/centos-bootc/centos-bootc:stream10` + +This template includes a Containerfile and a Github workflow for building the container image, signing, and proper metadata to be listed on [artifacthub](https://artifacthub.io/). As soon as the workflow is enabled in your repository, it will build the container image and push it to the Github Container Registry. + +# Prerequisites + +Working knowledge in the following topics: + +- Containers + - https://www.youtube.com/watch?v=SnSH8Ht3MIc + - https://www.mankier.com/5/Containerfile +- bootc + - https://containers.github.io/bootc/ +- Fedora Silverblue (and other Fedora Atomic variants) + - https://docs.fedoraproject.org/en-US/fedora-silverblue/ +- Github Workflows + - https://docs.github.com/en/actions/using-workflows + +# How to Use + +## Template + +Select `Use this Template` and create a new repository from it. To enable the workflows, you may need to go the `Actions` tab of the new repository and click to enable workflows. + +## Containerfile + +This file defines the operations used to customize the selected image. It contains examples of possible modifications, including how to: +- change the upstream from which the custom image is derived +- add additional RPM packages +- add binaries as a layer from other images + +## Building an ISO + +Modify `iso.toml` to point to your custom image before generating an ISO. + +- (Steps in progress) + +## Workflows + +### build.yml + +This workflow creates your custom OCI image and publishes it to the Github Container Registry (GHCR). By default, the image name will match the Github repository name. + +#### Container Signing + +Container signing is important for end-user security and is enabled on all Universal Blue images. It is recommended you set this up, and by default the image builds *will fail* if you don't. + +This provides users a method of verifying the image. + +1. Install the [cosign CLI tool](https://edu.chainguard.dev/open-source/sigstore/cosign/how-to-install-cosign/#installing-cosign-with-the-cosign-binary) + +2. Run inside your repo folder: + + ```bash + cosign generate-key-pair + ``` + + + - Do NOT put in a password when it asks you to, just press enter. The signing key will be used in GitHub Actions and will not work if it is encrypted. + +> [!WARNING] +> Be careful to *never* accidentally commit `cosign.key` into your git repo. + +3. Add the private key to GitHub + + - This can also be done manually. Go to your repository settings, under Secrets and Variables -> Actions + ![image](https://user-images.githubusercontent.com/1264109/216735595-0ecf1b66-b9ee-439e-87d7-c8cc43c2110a.png) + Add a new secret and name it `SIGNING_SECRET`, then paste the contents of `cosign.key` into the secret and save it. Make sure it's the .key file and not the .pub file. Once done, it should look like this: + ![image](https://user-images.githubusercontent.com/1264109/216735690-2d19271f-cee2-45ac-a039-23e6a4c16b34.png) + + - (CLI instructions) If you have the `github-cli` installed, run: + + ```bash + gh secret set SIGNING_SECRET < cosign.key + ``` + +4. Commit the `cosign.pub` file to the root of your git repository. + +# Community + +- [**bootc discussion forums**](https://github.com/containers/bootc/discussions) - Nothing in this template is ublue specific, the upstream bootc project has a discussions forum where custom image builders can hang out and ask questions. +- Index your image on [artifacthub.io](https://artifacthub.io), use the `artifacthub-repo.yml` file at the root to verify yourself as the publisher. [Discussion thread](https://universal-blue.discourse.group/t/listing-your-custom-image-on-artifacthub/6446) + +## Community Examples + +- [m2os](https://github.com/m2giles/m2os) +- [bos](https://github.com/bsherman/bos) +- [homer](https://github.com/bketelsen/homer/) diff --git a/artifacthub-repo.yml b/artifacthub-repo.yml new file mode 100644 index 0000000..c08d0b4 --- /dev/null +++ b/artifacthub-repo.yml @@ -0,0 +1,8 @@ +# This file is completely optional, but if you want to index your image on https://artifacthub.io/ you can +# Sign up and add the Repository ID to the right field. Owners fields are optional. +# Examples: https://artifacthub.io/packages/search?ts_query_web=ublue&sort=relevance&page=1 + +repositoryID: my-custom-id-here # Fill in with your own credentials +owners: # (optional, used to claim repository ownership) + - name: My Name + email: my_email@email.com diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..27048e0 --- /dev/null +++ b/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -ouex pipefail + +### Install packages + +# Packages can be installed from any enabled yum repo on the image. +# RPMfusion repos are available by default in ublue main images +# List of rpmfusion packages can be found here: +# https://mirrors.rpmfusion.org/mirrorlist?path=free/fedora/updates/39/x86_64/repoview/index.html&protocol=https&redirect=1 + +# this installs a package from fedora repos +dnf install -y tmux + +# Use a COPR Example: +# +# dnf5 -y copr enable ublue-os/staging +# dnf5 -y install package +# Disable COPRs so they don't end up enabled on the final image: +# dnf5 -y copr disable ublue-os/staging + +#### Example for enabling a System Unit File + +systemctl enable podman.socket diff --git a/image.toml b/image.toml new file mode 100644 index 0000000..7478400 --- /dev/null +++ b/image.toml @@ -0,0 +1,3 @@ +[[customizations.filesystem]] +mountpoint = "/" +minsize = "20 GiB" diff --git a/iso.toml b/iso.toml new file mode 100644 index 0000000..1b3084d --- /dev/null +++ b/iso.toml @@ -0,0 +1,19 @@ +[customizations.installer.kickstart] +contents = """ +%post +bootc switch --mutate-in-place --transport registry ghcr.io/yourname/yourusername:latest +%end +""" + +[customizations.installer.modules] +enable = [ + "org.fedoraproject.Anaconda.Modules.Storage" +] +disable = [ + "org.fedoraproject.Anaconda.Modules.Network", + "org.fedoraproject.Anaconda.Modules.Security", + "org.fedoraproject.Anaconda.Modules.Services", + "org.fedoraproject.Anaconda.Modules.Users", + "org.fedoraproject.Anaconda.Modules.Subscription", + "org.fedoraproject.Anaconda.Modules.Timezone" +]