Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/build-boxkit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,26 @@ jobs:
labels: |
io.artifacthub.package.readme-url=https://raw.githubusercontent.com/ublue-os/boxkit/main/README.md

# Compute SOURCE_DATE_EPOCH for reproducible builds
- name: Compute SOURCE_DATE_EPOCH
id: epoch
run: echo "epoch=$(date -d '${{ github.event.head_commit.timestamp }}' +%s)" >> $GITHUB_OUTPUT
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github.event.head_commit.timestamp is not present for pull_request, schedule, or workflow_dispatch events, so this step will fail (and block PR builds). Compute SOURCE_DATE_EPOCH from the checked-out commit instead (e.g., via git show -s --format=%ct $GITHUB_SHA or similar), with a safe fallback when the timestamp cannot be determined.

Suggested change
run: echo "epoch=$(date -d '${{ github.event.head_commit.timestamp }}' +%s)" >> $GITHUB_OUTPUT
run: |
commit_ts=$(git show -s --format=%ct "$GITHUB_SHA" 2>/dev/null || echo '')
if [ -z "$commit_ts" ]; then
echo "Warning: could not determine commit timestamp, falling back to current time"
commit_ts=$(date +%s)
fi
echo "epoch=$commit_ts" >> "$GITHUB_OUTPUT"

Copilot uses AI. Check for mistakes.

# Build image using Buildah action
- name: Build Image
id: build_image
uses: redhat-actions/buildah-build@7a95fa7ee0f02d552a32753e7414641a04307056 # v2
with:
containerfiles: |
./ContainerFiles/${{ matrix.containerfile }}
./Containerfiles/${{ matrix.containerfile }}
image: ${{ matrix.containerfile }}
tags: ${{ env.IMAGE_TAGS }}
labels: ${{ steps.meta.outputs.labels }}
oci: true
oci: false
extra-args: |
--skip-unused-stages=false
build-args: |
SOURCE_DATE_EPOCH=${{ steps.epoch.outputs.epoch }}

# 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
Expand All @@ -79,6 +88,8 @@ jobs:
registry: ${{ steps.registry_case.outputs.lowercase }}
username: ${{ env.REGISTRY_USER }}
password: ${{ env.REGISTRY_PASSWORD }}
extra-args: |
--compression-format=zstd:chunked

- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
Expand Down
15 changes: 0 additions & 15 deletions ContainerFiles/boxkit

This file was deleted.

15 changes: 0 additions & 15 deletions ContainerFiles/fedora-example

This file was deleted.

35 changes: 35 additions & 0 deletions Containerfiles/boxkit
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM quay.io/toolbx-images/alpine-toolbox:edge AS builder

# Copy the setup scripts and package list
COPY scripts/boxkit.sh /
COPY scripts/distrobox-shims.sh /
COPY scripts/chunkah-tag.sh /
COPY packages/boxkit.packages /

# Run the setup scripts and tag files for chunkah
RUN chmod +x boxkit.sh distrobox-shims.sh chunkah-tag.sh && \
/boxkit.sh && \
/chunkah-tag.sh && \
rm /boxkit.sh /distrobox-shims.sh /chunkah-tag.sh /boxkit.packages

# For CI builds with buildah, use chunkah rechunking
# For local podman builds, use: podman build --target=builder -f Containerfiles/boxkit -t boxkit:local .

# https://github.com/coreos/chunkah
FROM quay.io/jlebon/chunkah AS chunkah
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stage pulls the quay.io/jlebon/chunkah image by a mutable tag, introducing a third-party build-time dependency that has full control over chunkah build and the resulting out.ociarchive image. If this image or its registry namespace is compromised, an attacker could run arbitrary code in your CI build and produce malicious boxkit images without modifying this repository. To reduce supply-chain risk, pin this dependency to a specific image digest (or vetted vendor image) instead of a mutable tag and update the digest deliberately.

Copilot uses AI. Check for mistakes.

# Rechunk with content-based layers
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-0}
RUN --mount=from=builder,src=/,target=/chunkah,ro \
--mount=type=bind,target=/run/src,rw \
chunkah build \
--max-layers 64 \
--label=com.github.containers.toolbox=true \
--label=usage="This image is meant to be used with the toolbox or distrobox command" \
--label=summary="A cloud-native terminal experience" \
--label=maintainer=jorge.castro@gmail.com \
> /run/src/out.ociarchive

# Deploy final chunked image
FROM oci-archive:out.ociarchive
36 changes: 36 additions & 0 deletions Containerfiles/fedora-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM quay.io/fedora/fedora-toolbox:latest AS builder

# Copy the setup scripts and package list
COPY scripts/fedora-example.sh /
COPY scripts/distrobox-shims.sh /
COPY scripts/chunkah-tag.sh /
COPY packages/fedora-example.packages /

# Run the setup scripts and tag files for chunkah
RUN chmod +x fedora-example.sh distrobox-shims.sh chunkah-tag.sh && \
/fedora-example.sh && \
/chunkah-tag.sh && \
rm /fedora-example.sh /distrobox-shims.sh /chunkah-tag.sh /fedora-example.packages

# For CI builds with buildah, use chunkah rechunking
# For local podman builds, use: podman build --target=builder -f Containerfiles/fedora-example -t fedora-example:local .

# https://github.com/coreos/chunkah
FROM quay.io/jlebon/chunkah AS chunkah
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stage pulls the quay.io/jlebon/chunkah image by a mutable tag, introducing a third-party build-time dependency that has full control over chunkah build and the generated out.ociarchive image. If this image or registry namespace is ever compromised, an attacker could execute arbitrary code during CI builds and ship backdoored artifacts without changes to this repo. To mitigate this supply-chain risk, pin the image to a specific immutable digest (and/or an officially maintained vendor namespace) and update it explicitly as needed instead of relying on a mutable tag.

Copilot uses AI. Check for mistakes.

# Rechunk with content-based layers
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-0}
RUN --mount=from=builder,src=/,target=/chunkah,ro \
--mount=type=bind,target=/run/src,rw \
chunkah build \
--max-layers 64 \
--label=com.github.containers.toolbox=true \
--label=name=fedora-toolbox \
--label=usage="This image is meant to be used with the toolbox or distrobox command" \
--label=summary="An example ContainerFile to demonstrate multiple image builds." \
--label=maintainer=faeizmahrus@outlook.com \
> /run/src/out.ociarchive

# Deploy final chunked image
FROM oci-archive:out.ociarchive
38 changes: 38 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env -S just --justfile

# boxkit - Justfile for local development and testing
# Container provisioning logic lives in scripts/

# Build boxkit image locally (without chunkah - chunkah requires buildah)
build-boxkit:
podman build --target=builder -f Containerfiles/boxkit -t boxkit:local .

# Build fedora-example image locally (without chunkah - chunkah requires buildah)
build-fedora-example:
podman build --target=builder -f Containerfiles/fedora-example -t fedora-example:local .

# Build all images
build-all: build-boxkit build-fedora-example

# Run boxkit in distrobox
run-boxkit:
@distrobox create -i boxkit:local -n boxkit 2>/dev/null || true
distrobox enter boxkit

# Run fedora-example in distrobox
run-fedora-example:
@distrobox create -i fedora-example:local -n fedora-example 2>/dev/null || true
distrobox enter fedora-example

# Clean built images
clean:
podman rmi -f boxkit:local fedora-example:local 2>/dev/null || true
@echo "✓ Local images cleaned"

# Clean distrobox containers
clean-distrobox:
@distrobox rm -f boxkit fedora-example 2>/dev/null || true
@echo "✓ Distrobox containers removed"

# Full cleanup
clean-all: clean clean-distrobox
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ Tag your image with `boxkit` to share with others!

### How everything is organized

- The ContainerFiles for the custom images are stored in the `ContainerFiles/` folder.
- The Containerfiles for the custom images are stored in the `Containerfiles/` folder.
- The setup scripts for the custom images (if needed) are stored in the `scripts/` folder.
- The package lists for the setup scripts (if needed) are stored in the `packages/` folder.
- The Justfile provides local build commands (`just build-boxkit`, `just build-all`).
- The Github workflow that generates the images is `.github/workflows/build-boxkit.yml`

### How to make your own images

1. Fork this repo.
2. Add the ContainerFiles for your custom images to the `ContainerFiles/` folder.
2. Add the Containerfiles for your custom images to the `Containerfiles/` folder.
3. Add the setup scripts you want to use for your custom images (if needed) to the `scripts/` folder.
4. Add the package list you want to use for your custom images (if needed) to the `packages/` folder.
5. Add the name of the ContainerFiles of your custom images to the following section in `build-boxkit.yml`:
5. Add the name of the Containerfiles of your custom images to the following section in `build-boxkit.yml`:

```yaml
jobs:
Expand All @@ -50,11 +51,34 @@ jobs:
- You can choose to only generate a single custom image if you want.
- You can remove the boxkit and fedora-example images provided in the boxkit repo and only generate your own custom images.
- The `scripts/` and `packages/` folders are optional, you can generate your custom images without them, but they are highly recommended to use.
- The name of your custom image and ContainerFile **MUST** be the same. <br>
- The name of your custom image and Containerfile **MUST** be the same. <br>

e.g. If you want to create a custom image named *appbox-debian*, the corresponding ContainerFile must be named `appbox-debian` and must be stored inside the `ContainerFiles/` folder.
e.g. If you want to create a custom image named *appbox-debian*, the corresponding Containerfile must be named `appbox-debian` and must be stored inside the `Containerfiles/` folder.
- The URL for the generated images will be `ghcr.io/<username>/<image_name>` by default.

## zstd:chunked and Content-Based Layers

boxkit images use [chunkah](https://github.com/coreos/chunkah) for content-based layer splitting and `zstd:chunked` compression. This significantly reduces bandwidth when pulling updated images by only fetching changed content.

**Benefits:**
- **Faster image pulls** - Only changed layers are downloaded
- **Reduced bandwidth usage** - Efficient delta transfers
- **Better layer reuse** - Package-based layers maximize caching across updates

**How it works:**
- The `scripts/chunkah-tag.sh` script tags all installed files with their package names using extended file attributes
- The `chunkah` tool uses these tags to create intelligent package-based layers (up to 64 layers max)
- Images are compressed with `zstd:chunked` for efficient partial pulls
- Works with any OCI-compliant registry and requires no special client support

**Supported distributions:**
- Alpine Linux (apk)
- Fedora/RHEL/CentOS (rpm/dnf/yum)
- Debian/Ubuntu (dpkg/apt)
- Arch Linux (pacman)

The script auto-detects your package manager and tags files accordingly. If using an unsupported distribution, chunkah will still work but layer splitting will be less optimal.

### Signing your images
Although optional, it is **Highly recommended** you use container signing for your images.
To sign your images, follow the steps below:
Expand Down
71 changes: 71 additions & 0 deletions scripts/chunkah-tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/bin/sh
# Tag files with package component names for chunkah content-based layering
# Supports: apk (Alpine), rpm (Fedora/RHEL), dpkg (Debian/Ubuntu), pacman (Arch)

set -euo pipefail
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script declares #!/bin/sh but uses set -euo pipefail; pipefail is not supported by all /bin/sh implementations (notably dash on Debian/Ubuntu, which you claim to support), causing the script to exit immediately. Either switch to a shell that supports pipefail (and ensure it exists in all target images) or make pipefail optional/conditional.

Suggested change
set -euo pipefail
set -eu
# Enable pipefail if supported by the current /bin/sh implementation
if (set -o pipefail) 2>/dev/null; then
set -o pipefail
fi

Copilot uses AI. Check for mistakes.

tag_files() {
pkgname="$1"
shift
for filepath in "$@"; do
if [ -f "$filepath" ]; then
setfattr -n user.component -v "$pkgname" "$filepath" 2>/dev/null || true
fi
done
}

if command -v apk >/dev/null 2>&1; then
echo "==> Detected Alpine (apk) - tagging files for chunkah"
apk add --no-cache attr
apk info -q | while read pkgname; do
tag_files "$pkgname" $(apk info -L "$pkgname" 2>/dev/null | sed 's|^|/|')
done
Comment on lines +20 to +22
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unquoted command substitution here causes word-splitting/globbing and can break on paths containing whitespace; it can also hit ARG_MAX for packages with many files. Prefer reading the file list line-by-line (and use read -r) rather than expanding the whole list into arguments.

Copilot uses AI. Check for mistakes.
apk del attr
echo "✓ Alpine file tagging complete"

elif command -v rpm >/dev/null 2>&1; then
echo "==> Detected RPM-based (dnf/yum) - tagging files for chunkah"
if command -v dnf5 >/dev/null 2>&1; then
dnf5 install -y attr
elif command -v dnf >/dev/null 2>&1; then
dnf install -y attr
else
yum install -y attr
fi

rpm -qa --qf '%{NAME}\n' | while read pkgname; do
tag_files "$pkgname" $(rpm -ql "$pkgname" 2>/dev/null)
done
Comment on lines +36 to +38
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: $(rpm -ql ...) is expanded unquoted into arguments, which is unsafe for whitespace and can exceed ARG_MAX for large packages. Prefer streaming the file list line-by-line (and use read -r).

Copilot uses AI. Check for mistakes.

if command -v dnf5 >/dev/null 2>&1; then
dnf5 remove -y attr
elif command -v dnf >/dev/null 2>&1; then
dnf remove -y attr
else
yum remove -y attr
fi
echo "✓ RPM file tagging complete"

elif command -v dpkg >/dev/null 2>&1; then
echo "==> Detected Debian-based (dpkg/apt) - tagging files for chunkah"
apt-get update && apt-get install -y attr
dpkg-query -W -f='${Package}\n' | while read pkgname; do
tag_files "$pkgname" $(dpkg -L "$pkgname" 2>/dev/null)
done
Comment on lines +52 to +54
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: $(dpkg -L ...) is expanded unquoted into arguments, which is unsafe for whitespace and can exceed ARG_MAX for packages with many files. Prefer streaming the file list line-by-line (and use read -r).

Copilot uses AI. Check for mistakes.
apt-get remove -y attr
echo "✓ Debian file tagging complete"

elif command -v pacman >/dev/null 2>&1; then
echo "==> Detected Arch (pacman) - tagging files for chunkah"
pacman -Sy --noconfirm attr
pacman -Qq | while read pkgname; do
tag_files "$pkgname" $(pacman -Ql "$pkgname" 2>/dev/null | awk '{print $2}')
Comment on lines +61 to +62
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: $(pacman -Ql ...) output is expanded unquoted into arguments, which is unsafe for whitespace and can exceed ARG_MAX. Prefer streaming the file list line-by-line (and use read -r).

Suggested change
pacman -Qq | while read pkgname; do
tag_files "$pkgname" $(pacman -Ql "$pkgname" 2>/dev/null | awk '{print $2}')
pacman -Qq | while read -r pkgname; do
pacman -Qlq "$pkgname" 2>/dev/null | while read -r filepath; do
tag_files "$pkgname" "$filepath"
done

Copilot uses AI. Check for mistakes.
done
pacman -R --noconfirm attr
echo "✓ Arch file tagging complete"

else
echo "WARNING: Unknown package manager detected"
echo "Chunkah will still work but layer splitting may be less optimal"
exit 0
fi
Loading