Compare commits

..

10 Commits

Author SHA1 Message Date
Jorge O. Castro acf114c416
feat: add copr example (#58)
And remove duplicate rpm-ostree example.
2025-01-11 21:25:40 -03:00
Tulip Blossom 4c20cc7a55
fix: signing also needs to be lowercase 😭 (#57) 2025-01-11 16:00:36 -05:00
Tulip Blossom 45680135b8
fix: registry-case action is still necessary (#56) 2025-01-11 15:50:43 -05:00
Tulip Blossom 8ce1cdb82a
fix: incorrect URL for bib image on justfile (#55) 2025-01-08 21:56:59 -03:00
Jorge O. Castro 51e4719366
Update README.md 2025-01-08 19:28:28 -05:00
Tulip Blossom 8c6b00ffd8
feat: add justfile pattern from M2's PRs to bluefin (#54) 2025-01-08 19:26:12 -05:00
Tulip Blossom bc92a516af
feat: (re)add registry login to push to GHCR (#53) 2025-01-08 19:15:48 -05:00
Tulip Blossom 93299922c7
fix: use IMAGE_NAME and IMAGE_DESC for variables + fix typo in containerfile (#51) 2025-01-08 19:15:28 -05:00
Tulip Blossom 4fa1d41a49
chore: add rechunk and maximize-build-storage examples on the build workflow (#52)
I believe these should be enabled by default, since they make builds a lot slower, but it is nice having some docs about it
2025-01-08 19:14:57 -05:00
Jorge O. Castro da39213f10
Update README.md (#50) 2025-01-05 10:37:26 -05:00
8 changed files with 293 additions and 13 deletions

View File

@ -14,8 +14,8 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
MY_IMAGE_NAME: "${{ github.event.repository.name }}" # the name of the image produced by this build, matches repo names IMAGE_NAME: "${{ github.event.repository.name }}" # the name of the image produced by this build, matches repo names
MY_IMAGE_DESC: "My Customized Universal Blue Image" IMAGE_DESC: "My Customized Universal Blue Image"
IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit 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/! 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/!
@ -38,6 +38,12 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 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 - name: Get current date
id: date id: date
run: | run: |
@ -93,6 +99,30 @@ jobs:
labels: ${{ steps.metadata.outputs.labels }} labels: ${{ steps.metadata.outputs.labels }}
oci: false 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 # 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 # 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 - name: Login to GitHub Container Registry
@ -103,14 +133,27 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} 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: Push To GHCR - name: Push To GHCR
uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 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) if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
id: push id: push
env:
REGISTRY_USER: ${{ github.actor }}
REGISTRY_PASSWORD: ${{ github.token }}
with: with:
registry: ${{ env.IMAGE_REGISTRY }} registry: ${{ steps.registry_case.outputs.lowercase }}
image: ${{ env.IMAGE_NAME }} image: ${{ env.IMAGE_NAME }}
tags: ${{ steps.metadata.outputs.tags }} 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 # 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 # your project for others to consume. You will need to create a public and private key
@ -123,7 +166,7 @@ jobs:
- name: Sign container image - name: Sign container image
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
run: | run: |
IMAGE_FULL="${{ env.IMAGE_REGISTRY }}/${IMAGE_NAME}" IMAGE_FULL="${{ steps.registry_case.outputs.lowercase }}/${IMAGE_NAME}"
for tag in ${{ steps.metadata.outputs.tags }}; do for tag in ${{ steps.metadata.outputs.tags }}; do
cosign sign -y --key env://COSIGN_PRIVATE_KEY $IMAGE_FULL:$tag cosign sign -y --key env://COSIGN_PRIVATE_KEY $IMAGE_FULL:$tag
done done

1
.gitignore vendored
View File

@ -1 +1,2 @@
cosign.key cosign.key
_build_*

View File

@ -1,4 +1,4 @@
FROM ghcr.io/ublue-os/ublue-os/silverblue-main:latest FROM ghcr.io/ublue-os/silverblue-main:latest
## Other possible base images include: ## Other possible base images include:
# FROM ghcr.io/ublue-os/bazzite:stable # FROM ghcr.io/ublue-os/bazzite:stable

203
Justfile Normal file
View File

@ -0,0 +1,203 @@
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}" \
${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")

View File

@ -12,11 +12,6 @@ This repository is meant to be a template for building your own custom Universal
This template includes a Containerfile and a Github workflow for building the container image. As soon as the workflow is enabled in your repository, it will build the container image and push it to the Github Container Registry. This template includes a Containerfile and a Github workflow for building the container image. As soon as the workflow is enabled in your repository, it will build the container image and push it to the Github Container Registry.
# 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.
# Prerequisites # Prerequisites
Working knowledge in the following topics: Working knowledge in the following topics:
@ -44,6 +39,12 @@ This file defines the operations used to customize the selected image. It contai
- add additional RPM packages - add additional RPM packages
- add binaries as a layer from other images - 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 ## Workflows
### build.yml ### build.yml
@ -85,7 +86,13 @@ This provides users a method of verifying the image.
4. Commit the `cosign.pub` file to the root of your git repository. 4. Commit the `cosign.pub` file to the root of your git repository.
### Examples # 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.
## Community Examples
- [m2os](https://github.com/m2giles/m2os) - [m2os](https://github.com/m2giles/m2os)
- [bos](https://github.com/bsherman/bos) - [bos](https://github.com/bsherman/bos)
- [homer](https://github.com/bketelsen/homer/) - [homer](https://github.com/bketelsen/homer/)

View File

@ -12,8 +12,12 @@ set -ouex pipefail
# this installs a package from fedora repos # this installs a package from fedora repos
dnf install -y tmux dnf install -y tmux
# this would install a package from rpmfusion # Use a COPR Example:
# rpm-ostree install vlc #
# 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 #### Example for enabling a System Unit File

3
image.toml Normal file
View File

@ -0,0 +1,3 @@
[[customizations.filesystem]]
mountpoint = "/"
minsize = "20 GiB"

19
iso.toml Normal file
View File

@ -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"
]