diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 92c44a82..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# 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 index e8471f27..aa31bb56 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,26 +1,71 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:best-practices", + "config:best-practices" ], + "forkProcessing": "enabled", + "dependencyDashboard": true, "rebaseWhen": "never", + "git-submodules": { + "enabled": true + }, + + "regexManagers": [ + { + "description": "Track digests in image-versions.yaml", + "fileMatch": ["^image-versions\\.yaml$"], + "matchStrings": [ + "image: (?[^\\s]+)\\s+tag: (?[^\\s]+)\\s+digest: (?sha256:[a-f0-9]+)" + ], + "datasourceTemplate": "docker" + } + ], + "packageRules": [ { + "description": "Automatically automerge pin and pinDigest updates for all dependencies to maintain stability", + "matchUpdateTypes": ["pin", "pinDigest"], "automerge": true, - "matchUpdateTypes": ["pin", "pinDigest"] + "ignoreTests": true }, { + "description": "Disable automated container updates within GitHub workflows for security/drift prevention", "enabled": false, "matchUpdateTypes": ["digest", "pinDigest", "pin"], "matchDepTypes": ["container"], - "matchFileNames": [".github/workflows/**.yaml", ".github/workflows/**.yml"], + "matchFileNames": [".github/workflows/**.yaml", ".github/workflows/**.yml"] + }, + { + "description": "Automerge GitHub Actions digest updates in workflows for security parity", + "matchDatasources": ["github-tags"], + "matchFileNames": [".github/workflows/**"], + "matchUpdateTypes": ["digest", "pinDigest"], + "automerge": true, + "ignoreTests": true }, { + "description": "Automatically update and automerge all upstream digests in image-versions.yaml to ensure a sync matrix build", + "matchFileNames": ["image-versions.yaml"], + "matchUpdateTypes": ["digest"], "automerge": true, + "ignoreTests": true, + "groupName": "matrix-upstreams", + "commitMessagePrefix": "chore(deps): " + }, + { + "description": "Automatically update and automerge Bazzite-Deck upstream digests in Containerfile", "matchUpdateTypes": ["digest"], - "matchDepNames": ["ghcr.io/ublue-os/bazzite-deck"], + "matchDatasources": ["docker"], + "matchPackageNames": [ + "ghcr.io/ublue-os/bazzite-deck" + ], + "matchFileNames": ["Containerfile"], + "pinDigests": true, + "automerge": true, + "ignoreTests": true, + "commitMessagePrefix": "chore(deps): " } ] } \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f1f7a35e..c9bb9ae9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,23 @@ ---- name: Build Bazzite DX on: + pull_request: + branches: + - main + push: + branches: + - main + paths-ignore: # don't rebuild if only documentation has changed + - "**.md" repository_dispatch: types: - build + workflow_dispatch: # allow manually triggering builds + inputs: + build_untested: + description: "Build images marked as tested: false" + type: boolean + default: false merge_group: - pull_request: - workflow_dispatch: env: IMAGE_NAME: "${{ github.event.repository.name }}" # the name of the image produced by this build, matches repo names @@ -14,32 +25,44 @@ env: IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit ARTIFACTHUB_LOGO_URL: "https://avatars.githubusercontent.com/u/187439889?s=200&v=4" - # The tag used in the image from which we base of. - # ex.: ghcr.io/org/image:IMAGE_SOURCE_TAG - IMAGE_SOURCE_TAG: "latest" - SOURCE_ORG: "ublue-os" - SOURCE_REPO: "bazzite" - concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }}-${{ inputs.brand_name}}-${{ inputs.stream_name }} + group: ${{ github.workflow }}-${{ github.ref || github.run_id }}-${{ inputs.build_untested || 'false' }} cancel-in-progress: true jobs: - build_push: + prepare: + name: Prepare Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + # Use yq to generate a JSON array of objects from image-versions.yaml + # Filter for tested: true unless build_untested is true + BUILD_UNTESTED="${{ github.event.inputs.build_untested || 'false' }}" + + if [[ "$BUILD_UNTESTED" == "true" ]]; then + MATRIX=$(yq -o=json -I=0 '.images' image-versions.yaml | tr -d '\n') + else + MATRIX=$(yq -o=json -I=0 '.images | [.[] | select(.tested == true)]' image-versions.yaml | tr -d '\n') + fi + + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + + bluebuild: name: Build and push image - runs-on: ubuntu-24.04 + needs: prepare + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - base_image: - - bazzite-deck - - bazzite-deck-gnome - - bazzite-deck-nvidia-gnome - - bazzite-deck-nvidia + image: ${{ fromJSON(needs.prepare.outputs.matrix) }} env: - BASE_IMAGE: ${{ matrix.base_image }} + BASE_IMAGE: ${{ matrix.image.name }} permissions: contents: read @@ -49,27 +72,7 @@ jobs: steps: # These stage versions are pinned by https://github.com/renovatebot/renovate - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Show disk space - run: sudo df -h - - - name: Check if /mnt is there - id: check_mnt - run: | - if sudo mountpoint /mnt; then - echo "mnt_is_there=1" >>$GITHUB_OUTPUT - else - echo "mnt_is_there=0" >>$GITHUB_OUTPUT - fi - - - name: Free Disk Space (Ubuntu) - if: ${{ steps.check_mnt.outputs.mnt_is_there == '1' }} - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - - name: Mount BTRFS for podman storage - uses: ublue-os/container-storage-action@main - if: ${{ steps.check_mnt.outputs.mnt_is_there == '1' }} + uses: actions/checkout@v4 - name: Get current date id: date @@ -80,188 +83,182 @@ jobs: # https://linux.die.net/man/1/date echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" >> $GITHUB_OUTPUT - # OUTPUTS: - # - SOURCE_VERSION: version of the source image. Ex.: testing-41.20250312 - # - SOURCE_VERSION_MAJOR: major version. Used to identify big releases. Ex.: 41 + - name: Extract base image digest + id: extract_digest + shell: bash + run: | + # Using yq to extract fields separately + export BASE_IMAGE=$(yq '.images[] | select(.name == "${{ matrix.image.name }}") | .image' image-versions.yaml) + export BASE_TAG=$(yq '.images[] | select(.name == "${{ matrix.image.name }}") | .tag' image-versions.yaml) + export BASE_DIGEST=$(yq '.images[] | select(.name == "${{ matrix.image.name }}") | .digest' image-versions.yaml) + + # Fallback if entries are missing + if [[ -z "$BASE_IMAGE" || "$BASE_IMAGE" == "null" ]]; then + BASE_IMAGE="ghcr.io/ublue-os/${{ matrix.image.name }}" + fi + if [[ -z "$BASE_TAG" || "$BASE_TAG" == "null" ]]; then + BASE_TAG="latest" + fi + + echo "base_image=$BASE_IMAGE" >> $GITHUB_OUTPUT + echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT + echo "base_digest=$BASE_DIGEST" >> $GITHUB_OUTPUT + - name: Get image major version id: fetch_source_meta - env: - org: ${{ env.SOURCE_ORG }} - IMAGE_SOURCE_TAG: ${{ env.IMAGE_SOURCE_TAG || 'latest' }} run: | - set -x - # SOURCE_VERSION_MAJOR must be a number - declare -i SOURCE_VERSION_MAJOR=0 - - # There are some ways to get the major release from an image. - # First method: `skopeo inspect` and annotations. + set -ex + # Use the composite image for inspection + if [[ -n "${{ steps.extract_digest.outputs.base_digest }}" && "${{ steps.extract_digest.outputs.base_digest }}" != "null" ]]; then + export INSPECT_REF="docker://${{ steps.extract_digest.outputs.base_image }}@${{ steps.extract_digest.outputs.base_digest }}" + else + export INSPECT_REF="docker://${{ steps.extract_digest.outputs.base_image }}:${{ steps.extract_digest.outputs.base_tag }}" + fi SOURCE_VERSION=$( - skopeo inspect --no-tags --raw --config \ - "docker://ghcr.io/${org}/${BASE_IMAGE}:${IMAGE_SOURCE_TAG}" | \ - jq -r '.config.Labels["org.opencontainers.image.version"]' + skopeo inspect --no-tags --raw --config "$INSPECT_REF" | \ + jq -r '.config.Labels["org.opencontainers.image.version"]' 2>/dev/null || echo "null" ) - if [[ -z $SOURCE_VERSION ]]; then - echo "::error::$SOURCE_VERSION was not fetched correctly: $SOURCE_VERSION=${$SOURCE_VERSION}" - exit 1 + + # Fallback to latest tag if not found + if [[ -z $SOURCE_VERSION || "$SOURCE_VERSION" == "null" ]]; then + SOURCE_VERSION=$( + skopeo inspect --no-tags --raw --config \ + "docker://ghcr.io/ublue-os/${{ matrix.image.name }}:latest" | \ + jq -r '.config.Labels["org.opencontainers.image.version"]' 2>/dev/null || echo "null" + ) fi - echo "SOURCE_VERSION=$SOURCE_VERSION" >>$GITHUB_OUTPUT - SOURCE_VERSION_MAJOR=$([[ ${SOURCE_VERSION} =~ ^(.*-)?([[:digit:]]+) ]] && echo "${BASH_REMATCH[-1]}") - _status=$? - unset -v _tag + if [[ -z $SOURCE_VERSION || "$SOURCE_VERSION" == "null" ]]; then + echo "::error::SOURCE_VERSION could not be fetched." + exit 1 + fi + + # Source version might be "testing-43.20250312" or just "43.20250312" + # We want the numeric part only: "43.20250312" + CLEAN_VERSION=$(echo "$SOURCE_VERSION" | grep -oP '\d+\.\d+' || echo "$SOURCE_VERSION" | grep -oP '\d+') - if [[ $_status -ne 0 ]] || [[ -z ${SOURCE_VERSION_MAJOR} ]] || (( SOURCE_VERSION_MAJOR <= 0 )); then - echo "::error::SOURCE_VERSION_MAJOR was not fetched correctly: SOURCE_VERSION_MAJOR=${SOURCE_VERSION_MAJOR}" + # Major version is just the first number: "43" + SOURCE_VERSION_MAJOR=$(echo "$CLEAN_VERSION" | cut -d. -f1) + + if [[ -z ${SOURCE_VERSION_MAJOR} ]] || (( SOURCE_VERSION_MAJOR <= 0 )); then + echo "::error::SOURCE_VERSION_MAJOR ($SOURCE_VERSION_MAJOR) is not a valid number" exit 1 fi + # Extract NVIDIA Version labels (Upstream Alignment) + # Determine label key based on image variant + if [[ "${{ matrix.image.name }}" == *"nvidia-open"* ]]; then + export NVIDIA_LABEL_KEY="org.bazzite.kernel.nvidia" + else + export NVIDIA_LABEL_KEY="org.bazzite.kernel.nvidia_lts" + fi + + NVIDIA_VERSION=$( + skopeo inspect --no-tags --raw --config "$INSPECT_REF" | \ + jq -r ".config.Labels[\"$NVIDIA_LABEL_KEY\"]" 2>/dev/null || echo "null" + ) + + echo "SOURCE_VERSION=$CLEAN_VERSION" >>$GITHUB_OUTPUT echo "SOURCE_VERSION_MAJOR=$SOURCE_VERSION_MAJOR" >>$GITHUB_OUTPUT + echo "NVIDIA_VERSION=$NVIDIA_VERSION" >>$GITHUB_OUTPUT + echo "NVIDIA_LABEL_KEY=$NVIDIA_LABEL_KEY" >>$GITHUB_OUTPUT - name: Generate output image ref id: gen_image_ref run: | - echo "IMAGE_NAME=${BASE_IMAGE/bazzite/bazzite-dx}" | sed 's/-deck//' | tee -a "$GITHUB_ENV" + # Safe name translation using shell variables + export RAW_NAME="${{ matrix.image.name }}" + export OUT_NAME="${RAW_NAME/bazzite/bazzite-dx}" + echo "IMAGE_NAME=$OUT_NAME" | tee -a "$GITHUB_ENV" - # 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@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # 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=stable - type=raw,value=stable-${{ steps.fetch_source_meta.outputs.SOURCE_VERSION_MAJOR }} - type=raw,value=stable-${{ steps.fetch_source_meta.outputs.SOURCE_VERSION_MAJOR }}.{{date 'YYYYMMDD'}} - type=raw,value=${{ steps.fetch_source_meta.outputs.SOURCE_VERSION_MAJOR }} - type=raw,value=${{ steps.fetch_source_meta.outputs.SOURCE_VERSION_MAJOR }}.{{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=${{ steps.fetch_source_meta.outputs.SOURCE_VERSION }} - 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@7a95fa7ee0f02d552a32753e7414641a04307056 # v2.13 - 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 - build-args: | - BASE_IMAGE=ghcr.io/${{ env.SOURCE_ORG }}/${{ matrix.base_image }}:${{ env.IMAGE_SOURCE_TAG || 'latest' }} - IMAGE_NAME=${{ env.IMAGE_NAME }} - IMAGE_VENDOR=${{ github.repository_owner }} - - # 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@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 - if: github.event_name != 'pull_request' - 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 Image and Registry - id: lowercase - env: - IMAGE_REGISTRY: ${{ env.IMAGE_REGISTRY }} - IMAGE_NAME: ${{ env.IMAGE_NAME }} + - name: Surface Recipe run: | - set -x - echo "registry=${IMAGE_REGISTRY,,}" >> $GITHUB_OUTPUT - echo "image=${IMAGE_NAME,,}" >> $GITHUB_OUTPUT - - - name: Push To GHCR - uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 - if: github.event_name != 'pull_request' - id: push - env: - REGISTRY_USER: ${{ github.actor }} - REGISTRY_PASSWORD: ${{ github.token }} + set -ex + # 1. Image References + export FULL_IMAGE="${{ steps.extract_digest.outputs.full_image }}" + export NAME="${IMAGE_NAME}" + + # 2. Version Strings from fetch_source_meta + export VERSION_FULL="${{ steps.fetch_source_meta.outputs.SOURCE_VERSION }}" + export VERSION_MAJOR="${{ steps.fetch_source_meta.outputs.SOURCE_VERSION_MAJOR }}" + + # 3. Generate Clean Tags (uBlue Standard) + if [ "${{ matrix.image.tested }}" = "true" ]; then + export TAGS="latest,stable,${VERSION_FULL},stable-${VERSION_FULL}" + else + export TAGS="testing,testing-${VERSION_FULL}" + fi + + # 4. Patch Recipe + # 4. Patch Recipe + export BASE_IMAGE="${{ steps.extract_digest.outputs.base_image }}" + export BASE_TAG="${{ steps.extract_digest.outputs.base_tag }}" + export BASE_DIGEST="${{ steps.extract_digest.outputs.base_digest }}" + + if [[ -n "$BASE_DIGEST" && "$BASE_DIGEST" != "null" ]]; then + export IMAGE_VERSION_VAL="${BASE_TAG}@${BASE_DIGEST}" + else + export IMAGE_VERSION_VAL="${BASE_TAG}" + fi + + # Apply base fields first + # Apply base fields + yq -i ' + .name = env(NAME) | + .description = "${{ env.IMAGE_DESC }}" | + .base-image = env(BASE_IMAGE) | + .image-version = env(IMAGE_VERSION_VAL) + ' recipes/recipe.yml + + # Apply remaining labels and metadata + export NVIDIA_VER="${{ steps.fetch_source_meta.outputs.NVIDIA_VERSION }}" + export NVIDIA_KEY="${{ steps.fetch_source_meta.outputs.NVIDIA_LABEL_KEY }}" + + yq -i ' + .alt-tags = (env(TAGS) | split(",") | unique) | + .labels."io.artifacthub.package.logo-url" = "${{ env.ARTIFACTHUB_LOGO_URL }}" | + .labels."io.artifacthub.package.readme-url" = "https://raw.githubusercontent.com/${{ github.repository }}/main/README.md" | + .labels."io.artifacthub.package.maintainers" = "[{\"name\": \"nklowns\", \"email\": \"nklowns@users.noreply.github.com\"}]" | + .labels."io.artifacthub.package.keywords" = "bootc,bazzite,dx,ublue,universal-blue,fedora,gaming,developer" | + .labels."io.artifacthub.package.deprecated" = "false" | + .labels."io.artifacthub.package.prerelease" = "false" | + .labels."containers.bootc" = "1" | + .labels."ostree.linux" = "'$(uname -r)'" | + .labels."org.opencontainers.image.vendor" = "${{ github.repository_owner }}" | + .labels."org.opencontainers.image.licenses" = "Apache-2.0" | + .labels."org.opencontainers.image.url" = "https://dev.bazzite.gg" | + .labels."org.opencontainers.image.documentation" = "https://raw.githubusercontent.com/${{ github.repository }}/main/README.md" | + .labels."org.opencontainers.image.version" = "" + env(VERSION_FULL) | + .labels."org.opencontainers.image.revision" = "'${GITHUB_SHA}'" + ' recipes/recipe.yml + + # Conditionally apply NVIDIA labels if found in base image + if [[ -n "$NVIDIA_VER" && "$NVIDIA_VER" != "null" ]]; then + yq -i '.labels.[env(NVIDIA_KEY)] = env(NVIDIA_VER)' recipes/recipe.yml + fi + + cp recipes/recipe.yml recipes/build-recipe.yml + + - name: Build and Push with BlueBuild + uses: blue-build/github-action@v1.11 with: - registry: ${{ steps.lowercase.outputs.registry }} - image: ${{ steps.lowercase.outputs.image }} - tags: ${{ steps.metadata.outputs.tags }} - - # 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@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1 - if: github.event_name != 'pull_request' - - - name: Sign container image - if: github.event_name != 'pull_request' - env: - LOWERCASE_REGISTRY: ${{ steps.lowercase.outputs.registry }} - LOWERCASE_IMAGE: ${{ steps.lowercase.outputs.image }} - TAGS_TO_SIGN: ${{ steps.metadata.outputs.tags }} - TAGS: ${{ steps.push.outputs.digest }} - COSIGN_EXPERIMENTAL: false - COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} - run: | - IMAGE_FULL="${LOWERCASE_REGISTRY}/${LOWERCASE_IMAGE}" - for tag in ${TAGS_TO_SIGN}; do - cosign sign -y --key env://COSIGN_PRIVATE_KEY $IMAGE_FULL:$tag - done + recipe: build-recipe.yml + cosign_private_key: ${{ secrets.SIGNING_SECRET }} + registry_token: ${{ github.token }} + pr_event_number: ${{ github.event.number }} + skip_checkout: true + maximize_build_space: true + rechunk: false + build_chunked_oci: true + # Custom build options (conditional signing) + build_opts: "${{ secrets.SIGNING_SECRET == '' && '--no-sign' || '' }}" check: name: Check all builds successful if: always() runs-on: ubuntu-latest - needs: [build_push] + needs: [bluebuild] steps: - name: Check Jobs env: diff --git a/.gitignore b/.gitignore index bbc6166e..d8bbe6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ cosign.key _build_* +output/ +*.qcow2 +*.raw +*.iso +/.bluebuild*/ +/.bluebuild-scripts_* diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..bb4e6169 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,92 @@ +# Bazzite DX Copilot Instructions + +This document provides essential technical context for AI agents and developers contributing to the Bazzite DX repository. + +## Project Overview + +**Bazzite DX** is the developer-centric variant of Bazzite. It aims to deliver a "Developer Experience" (DX) equivalent to Bluefin DX and Aurora DX while maintaining Bazzite's gaming and HTPC optimizations. + +- **Build System**: BlueBuild (declarative YAML-based) + GitHub Actions. +- **Architectural Philosophy**: **Expert Monolith**. A high-performance, deduplicated layer that provides unique developer workarounds without redundant bloat. +- **Matrix Architecture**: Supports 25+ variants defined in `image-versions.yaml`. +- **Primary Stable Target**: The **Deck** family (`bazzite-dx-deck`) is the only verified stable path in the current matrix (`tested: true`). +- **Primary Audience**: Software engineers using Bazzite as their daily driver. +- **Contribution Model**: **Upstream Target**. All changes must adhere to uBlue-os image standards. + +## 🧠 Knowledge Base (MCP Context7) + +Refer to these libraries to understand upstream patterns and core infrastructure: + +### Core Infrastructure +- `/ublue-os/ucore`: Core uBlue infra. +- `/blue-build/cli` & `blue-build.org/reference`: Build engine documentation. +- `docs.fedoraproject.org/en-US/fedora-coreos`: Fedora CoreOS base. +- `/bootc-dev/bootc`: modern bootc logic. + +### Image Development & DX +- `/ublue-os/bazzite`: Steam Deck and gaming optimizations. +- `/ublue-os/bluefin` & `/ublue-os/aurora`: Reference DX patterns. +- `/ublue-os/bluefin-docs`, `/ublue-os/aurora-docs`, `docs.bazzite.gg`: Manuals. + +### Package Management & Stratification +- `/homebrew/brew`: Homebrew logic and CLI tool management. +- `ublue-os/tap`: Custom homebrew-tap for system-integration casks (e.g., VSCode). +- `/ublue-os/packages`: uBlue custom RPM specs. + +## đŸ§± Architectural Guardrails (MANDATORY) + +1. **Stratum Protection**: + - **Layered (Ostree)**: Performance tools (`bcc`, `bpftrace`, `kcli`) MUST stay in `recipe.yml`. They require kernel header synchronization. + - **Unlayered (Homebrew)**: VSCode (`visual-studio-code-linux`) and high-velocity CLI tools MUST stay in `bazzite-dx-tools.Brewfile`. +2. **Skeptical Deduplication (DRY)**: + - **MANDATORY**: Before adding any package or workaround, verify if it is already present in the Bazzite-base `Containerfile`. + - **Redundancy Purge**: If a feature is better handled by upstream Bazzite, remove it from the DX layer. +3. **Persistent Excellence**: Prefer automated Systemd services (e.g., `libvirt-workaround.service`) over imperative boot scripts or `ujust` toggles for core infrastructure stability. +4. **Bazaar Compliance**: Bazzite's `hooks.py` relies on VSCode being unlayered to prevent system-level update blocking. + +## đŸ› ïž Workflow Instructions + +### Core Configuration + +- `recipes/recipe.yml`: Declarative OCI image definition (BlueBuild). +- `Justfile`: Task automation for builds, linting, and formatting. +- `image-versions.yaml`: The "Smart Matrix" - manages all 25+ image variants and digests. +- `config/`: BlueBuild specific configurations. + +### Directories + +- `files/scripts/`: Modular build scripts (organized from `00-*` to `999-cleanup.sh`). +- `files/justfiles/`: **Custom Recipes**. Place new `ujust` tasks here for injection via the `justfiles` module. +- `files/system/`: **Direct Overrides**. Use for static configuration files overlaid onto the system root (`/`). Use this paths to **overwrite** existing Bazzite system files. + +## đŸ› ïž Development Workflow + +### Essential Commands + +```bash +# Build the OCI image locally using BlueBuild CLI +just build + +# Run shellcheck linting and recipe syntax validation +just check + +# Automatically format all Bash scripts using shfmt +just format +``` + +## 💡 Maintenance & Troubleshooting + +### The Smart Matrix logic +Bazzite-DX uses a dynamic matrix. When adding new packages: +1. Prefer adding to `recipes/recipe.yml` for declarative management. +2. Use `files/scripts/20-variant-adjust.sh` for logic that depends on `$IMAGE_NAME` (e.g., Deck vs. Desktop). +3. Ensure `999-cleanup.sh` remains minimal to avoid BlueBuild mount conflicts. + +### Security Compliance +All third-party repositories (COPR) MUST be disabled by the `90-validate-repos.sh` script before the image is finalized. + +## ✍ Contribution Guidelines + +1. **Surgical Precision**: Keep changes focused and minimal to facilitate upstream merging. +2. **Standard Alignment**: Ensure parity with Bluefin/Aurora DX patterns where applicable. +3. **Validation**: All PRs must pass `just check` before submission. diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 0fb18bc9..00000000 --- a/Containerfile +++ /dev/null @@ -1,16 +0,0 @@ -ARG BASE_IMAGE - -FROM scratch AS ctx - -COPY system_files /files -COPY build_files /build_files - -FROM ${BASE_IMAGE} - -ARG IMAGE_NAME="${IMAGE_NAME:-bazzite-dx}" -ARG IMAGE_VENDOR="{IMAGE_VENDOR:-ublue-os}" - -RUN --mount=type=tmpfs,dst=/tmp \ - --mount=type=bind,from=ctx,source=/,target=/run/context \ - mkdir -p /var/roothome && \ - /run/context/build_files/build.sh diff --git a/Justfile b/Justfile index fdf4a779..bdbb6fd7 100644 --- a/Justfile +++ b/Justfile @@ -1,203 +1,281 @@ export repo_organization := env("GITHUB_REPOSITORY_OWNER", "ublue-os") -export image_name := env("IMAGE_NAME", "bazzite-dx") +export default_image := env("IMAGE_NAME", "bazzite-deck") 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 ; ") } +export build_driver := if PODMAN =~ "docker" { "docker" } else { "podman" } +export PULL_POLICY := if PODMAN =~ "docker" { "missing" } else { "newer" } alias build-vm := build-qcow2 -alias rebuild-vm := rebuild-qcow2 alias run-vm := run-vm-qcow2 [private] default: @just --list -# Check Just Syntax +# Check development environment and image matrix status +[group('Just')] +status: + @echo "=== Image Configuration ===" + @echo "Default: {{ default_image }}" + @echo "Registry: {{ repo_organization }}" + @echo "Tag: {{ default_tag }}" + @echo "" + @echo "=== Matrix Insights (image-versions.yaml) ===" + @echo "Available variants: $(yq '.images[].name' image-versions.yaml | xargs)" + @echo "" + @echo "=== Local DX Images (localhost/bazzite-dx-*) ===" + @${PODMAN} images --filter "reference=localhost/bazzite-dx*" --format "table {{ '{{.Repository}}' }}\t{{ '{{.Tag}}' }}\t{{ '{{.ID}}' }}\t{{ '{{.CreatedSince}}' }}" + @echo "" + @echo "=== Tooling Versions ===" + @echo "Just: $(just --version | head -n1)" + @echo "Podman: $(${PODMAN} --version)" + @echo "BlueBuild: $(bluebuild --version 2>/dev/null || echo 'Not installed')" + +# Check Just Syntax and BlueBuild Recipe [group('Just')] check: #!/usr/bin/env bash find . -type f -name "*.just" | while read -r file; do - echo "Checking syntax: $file" - just --unstable --fmt --check -f $file + echo "Checking syntax: $file" + just --unstable --fmt --check -f $file done echo "Checking syntax: Justfile" just --unstable --fmt --check -f Justfile + if [ -f recipes/recipe.yml ]; then + echo "Validating BlueBuild recipe..." + bluebuild validate recipes/recipe.yml + fi + echo "Running ShellCheck on Bash scripts..." + just lint -# Fix Just Syntax +# Fix Just Syntax and Format scripts [group('Just')] fix: #!/usr/bin/env bash find . -type f -name "*.just" | while read -r file; do - echo "Checking syntax: $file" - just --unstable --fmt -f $file + echo "Fixing syntax: $file" + just --unstable --fmt -f $file done - echo "Checking syntax: Justfile" - just --unstable --fmt -f Justfile || { exit 1; } + echo "Fixing syntax: Justfile" + just --unstable --fmt -f Justfile + echo "Formatting Bash scripts..." + just format + +# Runs shell check on all Bash scripts (uses Container if local not found) +[group('Utility')] +lint: + #!/usr/bin/env bash + set -eoux pipefail + if ! command -v shellcheck &>/dev/null; then + echo "shellcheck not found locally. Running via ${PODMAN}..." + /usr/bin/find . -name "*.sh" -type f -not -path "./.bluebuild*" -exec ${PODMAN} run --rm -v "$PWD:/mnt:Z" docker.io/koalaman/shellcheck-alpine shellcheck /mnt/{} ';' + else + /usr/bin/find . -iname "*.sh" -type f -not -path "./.bluebuild*" -exec shellcheck "{}" ';' + fi + +# Runs shfmt on all Bash scripts (uses Container if local not found) +[group('Utility')] +format: + #!/usr/bin/env bash + set -eoux pipefail + if ! command -v shfmt &>/dev/null; then + echo "shfmt not found locally. Running via ${PODMAN}..." + /usr/bin/find . -name "*.sh" -type f -not -path "./.bluebuild*" -exec ${PODMAN} run --rm -v "$PWD:/mnt:Z" --entrypoint shfmt docker.io/mvdan/shfmt:latest -w /mnt/{} ';' + else + /usr/bin/find . -iname "*.sh" -type f -not -path "./.bluebuild*" -exec shfmt --write "{}" ';' + fi -# Clean Repo +# Clean project artifacts [group('Utility')] clean: #!/usr/bin/env bash set -euxo pipefail - touch _build - find *_build* -exec rm -rf {} \; + rm -rf _build/ + rm -rf .bluebuild/ rm -f previous.manifest.json rm -f changelog.md rm -f output.env + rm -rf output/ -# Sudo Clean Repo -[group('Utility')] +# Rebase the local system to the newly built image (local testing) +[group('Lifecycle')] +rebase-local target_image=default_image: + #!/usr/bin/env bash + set -euo pipefail + rm -f /tmp/{{ target_image }}.tar || true + {{ PODMAN }} save localhost/{{ target_image }}:latest --format oci-archive -o /tmp/{{ target_image }}.tar + sudo rpm-ostree rebase ostree-unverified-image:oci-archive:/tmp/{{ target_image }}.tar + rm -f /tmp/{{ target_image }}.tar + echo "Rebase complete. Please reboot to test your local image." + +# Rollback last transaction (Safety) +[group('Lifecycle')] +rollback: + sudo rpm-ostree rollback + +# Build image using BlueBuild CLI (Matrix aware) +[group('Build')] +build target_image=default_image tag=default_tag: (build-recipe target_image tag) + #!/usr/bin/env bash + set -euo pipefail + DX_NAME=$(echo "{{ target_image }}" | sed 's/^bazzite/bazzite-dx/') + bluebuild build --build-driver {{ build_driver }} --run-driver {{ build_driver }} .bluebuild/build-recipe.yml + # Tag precisely from the recipe-generated name + RECIPE_NAME=$(yq -r .name .bluebuild/build-recipe.yml) + echo "Tagging localhost/${RECIPE_NAME}:latest as localhost/${DX_NAME}:{{ tag }}" + ${PODMAN} tag localhost/${RECIPE_NAME}:latest localhost/${DX_NAME}:{{ tag }} + +# Build image without using cache +[group('Build')] +build-nocache target_image=default_image tag=default_tag: (build-recipe target_image tag) + #!/usr/bin/env bash + set -euo pipefail + DX_NAME=$(echo "{{ target_image }}" | sed 's/^bazzite/bazzite-dx/') + bluebuild build --no-cache --build-driver {{ build_driver }} --run-driver {{ build_driver }} .bluebuild/build-recipe.yml + # Tag precisely from the recipe-generated name + RECIPE_NAME=$(yq -r .name .bluebuild/build-recipe.yml) + echo "Tagging localhost/${RECIPE_NAME}:latest as localhost/${DX_NAME}:{{ tag }}" + ${PODMAN} tag localhost/${RECIPE_NAME}:latest localhost/${DX_NAME}:{{ tag }} + +# Generate build recipe with complete OCI metadata (Unified Logic) [private] -sudo-clean: - ${SUDOIF} just clean - -build $target_image=image_name $tag=default_tag: +build-recipe target_image tag: #!/usr/bin/env bash + set -euo pipefail + # Resolve and Patch + export BASE_IMAGE=$(yq ".images[] | select(.name == \"{{ target_image }}\") | .image" image-versions.yaml) + export BASE_TAG=$(yq ".images[] | select(.name == \"{{ target_image }}\") | .tag" image-versions.yaml) + export BASE_DIGEST=$(yq ".images[] | select(.name == \"{{ target_image }}\") | .digest" image-versions.yaml) + export IMAGE_NAME="bazzite-dx" + export IMAGE_DESC="Developer Experience (DX) layer for Bazzite. Matrix-ready and Universal." + export ARTIFACTHUB_LOGO_URL="https://avatars.githubusercontent.com/u/187439889?s=200&v=4" + export REPO_OWNER=$(git remote get-url origin | sed -E 's/.*[:\/](.*)\/(.*)\.git/\1/') + export KERNEL_RELEASE=$(uname -r) + export DATE_CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') + export VERSION_FULL="${BASE_TAG}.$(date +%Y%m%d)" + export REVISION=$(git rev-parse HEAD 2>/dev/null || echo 'local') + + if [[ -n "$BASE_DIGEST" && "$BASE_DIGEST" != "null" ]]; then + export IMAGE_VERSION_VAL="${BASE_TAG}@${BASE_DIGEST}" + else + export IMAGE_VERSION_VAL="${BASE_TAG}" + fi - # 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)") + if [ -z "${BASE_IMAGE}" ] || [ "${BASE_IMAGE}" == "null" ]; then + echo "Error: Image '{{ target_image }}' not found in image-versions.yaml" + exit 1 fi - ${PODMAN} build \ - "${BUILD_ARGS[@]}" \ - --pull=newer \ - --tag "${image_name}:${tag}" \ - . + echo "Generating build recipe for {{ target_image }} (Base: ${BASE_IMAGE}:${IMAGE_VERSION_VAL})..." + mkdir -p .bluebuild + yq ' + .name = env(IMAGE_NAME) | + .description = env(IMAGE_DESC) | + .base-image = env(BASE_IMAGE) | + .image-version = env(IMAGE_VERSION_VAL) + ' recipes/recipe.yml > .bluebuild/build-recipe.yml + + yq -i ' + .alt-tags = (["latest", "stable", "{{ tag }}", env(BASE_TAG)] | unique) | + .labels."io.artifacthub.package.logo-url" = env(ARTIFACTHUB_LOGO_URL) | + .labels."io.artifacthub.package.readme-url" = "https://raw.githubusercontent.com/" + env(REPO_OWNER) + "/bazzite-dx/main/README.md" | + .labels."io.artifacthub.package.maintainers" = "[{\"name\": \"nklowns\", \"email\": \"nklowns@users.noreply.github.com\"}]" | + .labels."io.artifacthub.package.keywords" = "bootc,bazzite,dx,ublue,universal-blue,fedora,gaming,developer" | + .labels."io.artifacthub.package.deprecated" = "false" | + .labels."io.artifacthub.package.prerelease" = "false" | + .labels."containers.bootc" = "1" | + .labels."ostree.linux" = env(KERNEL_RELEASE) | + .labels."org.opencontainers.image.vendor" = env(REPO_OWNER) | + .labels."org.opencontainers.image.licenses" = "Apache-2.0" | + .labels."org.opencontainers.image.url" = "https://dev.bazzite.gg" | + .labels."org.opencontainers.image.documentation" = "https://raw.githubusercontent.com/" + env(REPO_OWNER) + "/bazzite-dx/main/README.md" | + .labels."org.opencontainers.image.version" = env(VERSION_FULL) | + .labels."org.opencontainers.image.revision" = env(REVISION) + ' .bluebuild/build-recipe.yml -_rootful_load_image $target_image=image_name $tag=default_tag: +[private] +_rootful_load_image $target_image=default_image $tag=default_tag: #!/usr/bin/env bash set -euxo 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 + full_image="localhost/${target_image}:${tag}" + USER_IMG_ID=$(${PODMAN} images --filter reference="${full_image}" --format "'{{ '{{.ID}}' }}'") + if [ -n "$USER_IMG_ID" ]; then + echo "Loading ${full_image} (ID: $USER_IMG_ID) into rootful podman..." + COPYTMP=$(mktemp -p "${PWD}" -d -t _build_podman_scp.XXXXXXXXXX) + sudo TMPDIR=${COPYTMP} ${PODMAN} image scp ${UID}@localhost::"${full_image}" root@localhost::"${full_image}" + rm -rf "${COPYTMP}" else - # Make sure the image is present and/or up to date - ${SUDOIF} ${PODMAN} pull "${target_image}:${tag}" + echo "Image ${full_image} not found in user storage." + sudo ${PODMAN} pull "${full_image}" || (echo "Failed to pull image. Build it first." && exit 1) fi +[private] _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 \ + sudo rm -rf "output/${type}" "output/bootiso" || true + full_image="localhost/${target_image}:${tag}" + args="--type ${type} --progress verbose --use-librepo=True --rootfs=btrfs --local" + sudo ${PODMAN} run --rm -it --privileged --pull=${PULL_POLICY} --net=host \ --security-opt label=type:unconfined_t \ - -v $(pwd)/${config}:/config.toml:ro \ - -v $(pwd)/output:/output \ + -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) + "${bib_image}" ${args} "${full_image}" + sudo chown -R $USER:$USER output/ -[group('Build Virtual Machine Image')] -build-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "qcow2" "image.toml") +[group('Image Builders (BIB)')] +build-qcow2 $target_image=default_image $tag=default_tag: && (_build-bib target_image tag "qcow2" "disk_config/devel.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('Image Builders (BIB)')] +build-raw $target_image=default_image $tag=default_tag: && (_build-bib target_image tag "raw" "disk_config/devel.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") +[group('Image Builders (BIB)')] +build-iso $target_image=default_image $tag=default_tag: && (_build-bib target_image tag "iso" "disk_config/iso.toml") +[private] _run-vm $target_image $tag $type $config: #!/usr/bin/env bash set -euxo 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 + if [[ $type == iso ]]; then image_file="output/bootiso/install.iso"; fi + if [[ ! -f "${image_file}" ]]; then just "build-${type}" "$target_image" "$tag"; fi 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") + while grep -q :${port} <<< $(ss -tunalp); do port=$(( port + 1 )); done + echo "Using Port: ${port}. Connect to http://localhost:${port}" + ${PODMAN} run --rm --privileged --pull=newer \ + -p 127.0.0.1:${port}:8006 -p 127.0.0.1:2222:22 \ + -e CPU_CORES=4 -e RAM_SIZE=8G -e DISK_SIZE=64G -e TPM=Y -e GPU=Y \ + --device=/dev/kvm -v "${PWD}/${image_file}":"/boot.${type}" \ + docker.io/qemux/qemu & + xdg-open http://localhost:${port} || true + wait $! + +[group('VM Runners')] +run-vm-qcow2 $target_image=default_image $tag=default_tag: && (_run-vm target_image tag "qcow2" "disk_config/devel.toml") + +[group('VM Runners')] +run-vm-raw $target_image=default_image $tag=default_tag: && (_run-vm target_image tag "raw" "disk_config/devel.toml") + +[group('VM Runners')] +run-vm-iso $target_image=default_image $tag=default_tag: && (_run-vm target_image tag "iso" "disk_config/iso.toml") + +# Run a virtual machine using systemd-vmspawn +[group('VM Runners')] +spawn-vm rebuild="0" type="qcow2" ram="6G": + #!/usr/bin/env bash + set -euo pipefail + [ "{{ rebuild }}" -eq 1 ] && echo "Rebuilding the image" && just build-{{ type }} + systemd-vmspawn \ + -M "bootc-image" \ + --console=gui \ + --cpus=2 \ + --ram=$(echo {{ ram }}| /usr/bin/numfmt --from=iec) \ + --network-user-mode \ + --vsock=false --pass-ssh-key=false \ + -i ./output/**/*.{{ type }} diff --git a/README.md b/README.md index f730481b..08636b0e 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,96 @@ -# Bazzite Developer Edition +# Bazzite Developer Experience (DX) [![Build Bazzite DX](https://github.com/ublue-os/bazzite-dx/actions/workflows/build.yml/badge.svg)](https://github.com/ublue-os/bazzite-dx/actions/workflows/build.yml) -This is just bazzite, but with extra developer-specific tooling, aiming to match [Bluefin DX](https://docs.projectbluefin.io/bluefin-dx/) and [Aurora DX](https://docs.getaurora.dev/dx/aurora-dx-intro) in functionality +**Bazzite DX** is a premium, developer-focused edition of [Bazzite](https://bazzite.gg), meticulously engineered to provide the ultimate hybrid of a high-performance gaming platform and a world-class development workstation. It aligns with the "Developer Experience" philosophy of [Bluefin DX](https://docs.projectbluefin.io/bluefin-dx/) and [Aurora DX](https://docs.getaurora.dev/dx/aurora-dx-intro). -[`bazzite-gdx`](https://github.com/ublue-os/bazzite-gdx) will source from here and be focused for game development. +--- -## Installation +## đŸ—ïž Architectural Philosophy: The Smart Matrix -To rebase an existing Bazzite installation to Bazzite DX, use one of the following commands based on your current variant: +Bazzite-DX utilizes a **Smart Matrix** architecture powered by [BlueBuild](https://blue-build.org). This allows us to maintain a single source of truth while generating **25+ specialized image variants**, including support for Handhelds (Steam Deck/Legion Go), NVIDIA GPUs, and different Desktop Environments (KDE/GNOME). -**For KDE Plasma (default Bazzite):** -```bash -rpm-ostree rebase ostree-image-signed:docker://ghcr.io/ublue-os/bazzite-dx:stable -``` +- **Build System**: BlueBuild (declarative YAML-based) + GitHub Actions. +- **Package Strategy**: **Hybrid Stratification** (Layered for Kernel-bound tools | Unlayered for User-space IDEs/CLI). +- **Matrix Architecture**: Supports 25+ variants defined in `image-versions.yaml`. +- **Primary Audience**: Software engineers using Bazzite as their daily driver. +- **Contribution Model**: **Upstream Target**. All changes must adhere to uBlue-os image standards. -**For GNOME:** -```bash -rpm-ostree rebase ostree-image-signed:docker://ghcr.io/ublue-os/bazzite-dx-gnome:stable -``` +> [!IMPORTANT] +> **Recommended Target**: Always prioritize the `bazzite-deck` variants for development, as they are the primary verified target path (`tested: true`). -### NVIDIA Variants +--- -**For KDE Plasma with NVIDIA:** -```bash -rpm-ostree rebase ostree-image-signed:docker://ghcr.io/ublue-os/bazzite-dx-nvidia:stable -``` +## đŸ› ïž Core Capabilities -**For GNOME with NVIDIA:** -```bash -rpm-ostree rebase ostree-image-signed:docker://ghcr.io/ublue-os/bazzite-dx-nvidia-gnome:stable -``` +- **Tiered Workstation Experience**: Activate curated toolsets via `ujust bazzite-dx `: + - **`cli`**: Terminal excellence (bat, eza, fzf, zoxide, starship). + - **`ai`**: Modern AI workstation (Goose, llmfit, linux-mcp-server). + - **`cloud`**: Cloud-Native toolkit (kubectl, helm, k9s, terraform). +- **Ready-to-Code Automation**: No manual setup for system permissions. The image automatically grants `docker`, `libvirt`, and `dialout` access to wheel members. +- **Advanced Virtualization Overrides**: Automated persistence for `libvirt` and `swtpm` (via Systemd workarounds) ensuring a seamless VM experience without user intervention. +- **Hybrid DX Stack**: Strategic placement of tools for maximum performance. + - **Layered (OCI)**: High-performance observability (`bcc`, `bpftrace`, `bpftop`) and KVMFR modules. + - **Unlayered (Homebrew)**: Visual Studio Code and high-velocity CLI suites. +- **Always-On Typography**: Professional developer fonts (JetBrains Mono, Fira Code, Nerd Fonts) are pre-baked into the image layers. +- **Professional Flatpaks**: Essential DX apps pre-installed system-wide. -To skip signature verification (not recommended unless you know what you're doing and why you're doing it), replace `ostree-image-signed:docker://ghcr.io` with `ostree-unverified-registry:ghcr.io`. +--- -### ⚠ Important Desktop Environment Warning +## 🚀 Installation & Rebasing -**Do not switch between GNOME and KDE variants!** If you are currently running: -- **GNOME** (bazzite-gnome*): Only use the `-gnome` variants above -- **KDE Plasma** (standard bazzite): Only use the variants without `-gnome` in the name +To rebase your current Bazzite installation to the DX edition, execute the command corresponding to your variant. -Switching between desktop environments via rebase can break your installation and may require a complete reinstall. +> [!TIP] +> All images follow the pattern: `ghcr.io/ublue-os/bazzite-dx[-variant][-gnome]:stable` -After running the rebase command, reboot your system to complete the installation. +### Common Variants -## Acknowledgments +| Desktop | Hardware | DX Image (Target) | Base Image (Origin) | Status | +| :--- | :--- | :--- | :--- | :--- | +| **KDE** | Desktop/AMD | `bazzite-dx` | [bazzite](https://github.com/orgs/ublue-os/packages/container/package/bazzite) | Community | +| **KDE** | NVIDIA | `bazzite-dx-nvidia` | [bazzite-nvidia](https://github.com/orgs/ublue-os/packages/container/package/bazzite-nvidia) | Community | +| **KDE** | Steam Deck | `bazzite-dx-deck` | [bazzite-deck](https://github.com/orgs/ublue-os/packages/container/package/bazzite-deck) | **Recommended** | +| **GNOME**| Desktop/AMD | `bazzite-dx-gnome` | [bazzite-gnome](https://github.com/orgs/ublue-os/packages/container/package/bazzite-gnome) | Community | +| **GNOME**| NVIDIA | `bazzite-dx-gnome-nvidia` | [bazzite-gnome-nvidia](https://github.com/orgs/ublue-os/packages/container/package/bazzite-gnome-nvidia) | Community | +| **GNOME**| Steam Deck | `bazzite-dx-deck-gnome` | [bazzite-deck-gnome](https://github.com/orgs/ublue-os/packages/container/package/bazzite-deck-gnome) | **Recommended** | -This project is built upon the work from [amyos](https://github.com/astrovm/amyos) +> [!TIP] +> **Rebase Command**: `rpm-ostree rebase ostree-image-signed:docker://ghcr.io/ublue-os/[IMAGE_NAME]:stable` -## Metrics +### 🔧 Post-Rebase Configuration -![Alt](https://repobeats.axiom.co/api/embed/8568b042f7cfba9dd477885ed5ee6573ab78bb5e.svg "Repobeats analytics image") +Bazzite-DX is engineered to be **Ready-to-Code** out of the box. + +1. **Automatic Permissions**: After rebasing and rebooting, your user is automatically added to the necessary groups (`docker`, `libvirt`, `dialout`). +2. **Workstation Flavors**: To activate your preferred developer toolset, use: + ```bash + ujust bazzite-dx + ``` + *Available flavors: `cli`, `ai`, `cloud`, `all`.* + +> [!CAUTION] +> **Desktop Environment Lock**: Do not switch between GNOME and KDE variants via rebase. Always stay within the same DE family to avoid internal configuration conflicts. + +--- + +## đŸ‘©â€đŸ’» Development Workflow + +The project uses `Just` as the primary task runner. + +| Command | Description | +| :--- | :--- | +| `just build` | Build the OCI image locally using BlueBuild CLI | +| `just check` | Run `shellcheck` linting and recipe syntax validation | +| `just format` | Automatically format all Bash scripts using `shfmt` | +| `just status` | Display the current image matrix from `image-versions.yaml` | +| `just rebase-local` | Rebase the current system to the local build for testing | + +--- + +## ✍ Contribution Guidelines + +We welcome contributions following the "Surgical Precision" rule: +1. **Upstream First**: If a fix benefits the entire Bazzite community, submit it to [Bazzite Upstream](https://github.com/ublue-os/bazzite) instead. +2. **Modular Logic**: Keep logic changes within `files/scripts/` to maintain matrix compatibility. +3. **Validation**: All PRs must pass `just check` without warnings. diff --git a/build_files/00-image-info.sh b/build_files/00-image-info.sh deleted file mode 100755 index 59467ca3..00000000 --- a/build_files/00-image-info.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -set -eoux pipefail - -IMAGE_INFO="/usr/share/ublue-os/image-info.json" -IMAGE_REF="ostree-image-signed:docker://ghcr.io/$IMAGE_VENDOR/$IMAGE_NAME" - -# image-info File -sed -i 's/"image-name": [^,]*/"image-name": "'"$IMAGE_NAME"'"/' $IMAGE_INFO -sed -i 's|"image-ref": [^,]*|"image-ref": "'"$IMAGE_REF"'"|' $IMAGE_INFO - -# OS Release File -sed -i "s/^VARIANT_ID=.*/VARIANT_ID=$IMAGE_NAME/" /usr/lib/os-release - -# KDE About page -# We don't want to edit an unexisting file on gnome variants -if [[ "$IMAGE_NAME" != *gnome* ]]; then - sed -i "s|^Website=.*|Website=https://dev.bazzite.gg|" /etc/xdg/kcm-about-distrorc - if [[ "$IMAGE_NAME" != *nvidia* ]]; then - sed -i "s/^Variant=.*/Variant=Developer Experience/" /etc/xdg/kcm-about-distrorc - else - sed -i "s/^Variant=.*/Variant=Developer Experience (NVIDIA)/" /etc/xdg/kcm-about-distrorc - fi -fi diff --git a/build_files/20-install-apps.sh b/build_files/20-install-apps.sh deleted file mode 100755 index b49edf56..00000000 --- a/build_files/20-install-apps.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/bash -set -xeuo pipefail - -dnf5 install -y \ - android-tools \ - bcc \ - bpftop \ - bpftrace \ - ccache \ - flatpak-builder \ - git-subtree \ - nicstat \ - numactl \ - podman-machine \ - podman-tui \ - python3-ramalama \ - qemu-kvm \ - restic \ - rclone \ - sysprof \ - tiptop \ - usbmuxd \ - zsh - -# Restore UUPD update timer and Input Remapper -sed -i 's@^NoDisplay=true@NoDisplay=false@' /usr/share/applications/input-remapper-gtk.desktop -systemctl enable input-remapper.service -systemctl enable uupd.timer - -# Remove -deck specific changes to allow for login screens and session selection in settings -rm -f /etc/sddm.conf.d/steamos.conf -rm -f /etc/sddm.conf.d/virtualkbd.conf -rm -f /etc/sddm.conf.d/zz-steamos-autologin.conf -rm -f /usr/share/gamescope-session-plus/bootstrap_steam.tar.gz -systemctl disable bazzite-autologin.service -dnf5 remove -y steamos-manager - -if [[ "$IMAGE_NAME" == *gnome* ]]; then - # Remove SDDM and re-enable GDM on GNOME builds. - dnf5 remove -y \ - sddm - - systemctl enable gdm.service -else - # Re-enable logout and switch user functionality in KDE - sed -i -E \ - -e 's/^(action\/switch_user)=false/\1=true/' \ - -e 's/^(action\/start_new_session)=false/\1=true/' \ - -e 's/^(action\/lock_screen)=false/\1=true/' \ - -e 's/^(kcm_sddm\.desktop)=false/\1=true/' \ - -e 's/^(kcm_plymouth\.desktop)=false/\1=true/' \ - /etc/xdg/kdeglobals -fi - - -dnf5 install --enable-repo="copr:copr.fedorainfracloud.org:ublue-os:packages" -y \ - ublue-setup-services - -# Adding repositories should be a LAST RESORT. Contributing to Terra or `ublue-os/packages` is much preferred -# over using random coprs. Please keep this in mind when adding external dependencies. -# If adding any dependency, make sure to always have it disabled by default and _only_ enable it on `dnf install` - -dnf5 config-manager addrepo --set=baseurl="https://packages.microsoft.com/yumrepos/vscode" --id="vscode" -dnf5 config-manager setopt vscode.enabled=0 -# FIXME: gpgcheck is broken for vscode due to it using `asc` for checking -# seems to be broken on newer rpm security policies. -dnf5 config-manager setopt vscode.gpgcheck=0 -dnf5 install --nogpgcheck --enable-repo="vscode" -y \ - code - -docker_pkgs=( - containerd.io - docker-buildx-plugin - docker-ce - docker-ce-cli - docker-compose-plugin -) -dnf5 config-manager addrepo --from-repofile="https://download.docker.com/linux/fedora/docker-ce.repo" -dnf5 config-manager setopt docker-ce-stable.enabled=0 -dnf5 install -y --enable-repo="docker-ce-stable" "${docker_pkgs[@]}" || { - # Use test packages if docker pkgs is not available for f42 - if (($(lsb_release -sr) == 42)); then - echo "::info::Missing docker packages in f42, falling back to test repos..." - dnf5 install -y --enablerepo="docker-ce-test" "${docker_pkgs[@]}" - fi -} - -# Load iptable_nat module for docker-in-docker. -# See: -# - https://github.com/ublue-os/bluefin/issues/2365 -# - https://github.com/devcontainers/features/issues/1235 -mkdir -p /etc/modules-load.d && cat >>/etc/modules-load.d/ip_tables.conf <>/usr/lib/tmpfiles.d/opt-fix.conf -done - -log "Fix completed" diff --git a/build_files/999-cleanup.sh b/build_files/999-cleanup.sh deleted file mode 100755 index 0ebeaadc..00000000 --- a/build_files/999-cleanup.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/bash -set -euo pipefail - -trap '[[ $BASH_COMMAND != echo* ]] && [[ $BASH_COMMAND != log* ]] && echo "+ $BASH_COMMAND"' DEBUG - -log() { - echo "=== $* ===" -} - -log "Starting system cleanup" - -# Clean package manager cache -dnf5 clean all - -# Clean temporary files -rm -rf /tmp/* - -# Cleanup the entirety of `/var`. -# None of these get in the end-user system and bootc lints get super mad if anything is in there -rm -rf /var -mkdir -p /var - -# Commit and lint container -bootc container lint || true - -log "Cleanup completed" diff --git a/build_files/build.sh b/build_files/build.sh deleted file mode 100755 index 2d98c0ed..00000000 --- a/build_files/build.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -set -eo pipefail - -CONTEXT_PATH="$(realpath "$(dirname "$0")/..")" # should return /run/context -BUILD_SCRIPTS_PATH="$(realpath "$(dirname $0)")" -MAJOR_VERSION_NUMBER="$(sh -c '. /usr/lib/os-release ; echo $VERSION_ID')" -SCRIPTS_PATH="$(realpath "$(dirname "$0")/scripts")" -export CONTEXT_PATH -export SCRIPTS_PATH -export MAJOR_VERSION_NUMBER - -run_buildscripts_for() { - WHAT=$1 - shift - # Complex "find" expression here since there might not be any overrides - # Allows us to numerically sort scripts by stuff like "01-packages.sh" or whatever - # CUSTOM_NAME is required if we dont need or want the automatic name - find "${BUILD_SCRIPTS_PATH}/$WHAT" -maxdepth 1 -iname "*-*.sh" -type f -print0 | sort --zero-terminated --sort=human-numeric | while IFS= read -r -d $'\0' script ; do - if [ "${CUSTOM_NAME}" != "" ] ; then - WHAT=$CUSTOM_NAME - fi - printf "::group:: ===$WHAT-%s===\n" "$(basename "$script")" - "$(realpath $script)" - printf "::endgroup::\n" - done -} - -copy_systemfiles_for() { - WHAT=$1 - shift - DISPLAY_NAME=$WHAT - if [ "${CUSTOM_NAME}" != "" ] ; then - DISPLAY_NAME=$CUSTOM_NAME - fi - printf "::group:: ===%s-file-copying===\n" "${DISPLAY_NAME}" - cp -avf "${CONTEXT_PATH}/$WHAT/." / - printf "::endgroup::\n" -} - -CUSTOM_NAME="base" -copy_systemfiles_for files -run_buildscripts_for . -CUSTOM_NAME= diff --git a/build_files/scripts/log.sh b/build_files/scripts/log.sh deleted file mode 100644 index 0cea6bed..00000000 --- a/build_files/scripts/log.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -group() { - WHAT=$1 - shift - echo "::group:: === $WHAT ===" -} - -log() { - echo "=== $* ===" -} diff --git a/cosign.pub b/cosign.pub index bd5b1927..b5873868 100644 --- a/cosign.pub +++ b/cosign.pub @@ -1,4 +1,4 @@ -----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHLRpBfPRYiMl9wb7s6fx47PzzNWu -3zyJgXhWEvxoOgwv9CpwjbvUwR9qHxNMWkJhuGE6cjDA2hpy1I6NbA+24Q== +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETBrtDjRchEXIrbpj+z28Yb0JeJnz +f7l69lPeClCIIC5KSdJAP6UveUpVYxV7D5eNhWAssJBDDa8zlc+DSjba1g== -----END PUBLIC KEY----- diff --git a/disk_config/devel.toml b/disk_config/devel.toml new file mode 100644 index 00000000..a44ff88a --- /dev/null +++ b/disk_config/devel.toml @@ -0,0 +1,9 @@ +[[customizations.filesystem]] +mountpoint = "/" +minsize = "20 GiB" + +[[customizations.user]] +name = "bazzite" +description = "Bazzite User" +password = "$6$qau6Anlc06ENjAsR$di1/1thxswyPXj0SgaNu4tQD9ZaWX.IGx0MxSUGLA6qMy/TyK2RYzKzewwgmKnLZN4KV3GYi1si47Les0rDX9/" # 'bazzite' +groups = ["wheel"] diff --git a/iso.toml b/disk_config/iso.toml similarity index 100% rename from iso.toml rename to disk_config/iso.toml diff --git a/image.toml b/disk_config/prod.toml similarity index 100% rename from image.toml rename to disk_config/prod.toml diff --git a/files/justfiles/95-bazzite-dx.just b/files/justfiles/95-bazzite-dx.just new file mode 100644 index 00000000..40404fc3 --- /dev/null +++ b/files/justfiles/95-bazzite-dx.just @@ -0,0 +1,112 @@ +# vim: set ft=make : +# --- Bazzite-DX: The Transformation Interface --- + +# Audit the current Bazzite-DX workstation state +[group('System')] +dx-doctor: + #!/usr/bin/bash + echo "=== Bazzite-DX: Medical Report ===" + echo "1. Core Kernel Matrix:" + cat /proc/cmdline | grep -iE --color "iommu|kvm|preempt" || echo "Warning: Specialized kargs not detected!" + + echo -e "\n2. Architecture & Image:" + [[ -f /usr/share/ublue-os/image-info.json ]] && jq -C . /usr/share/ublue-os/image-info.json + + echo -e "\n3. Developer Group Sync:" + DX_GROUPS=("docker" "incus-admin" "libvirt" "dialout" "input" "video" "render" "plugdev") + for grp in "${DX_GROUPS[@]}"; do + if groups "$USER" | grep -q "\b$grp\b"; then + echo "[ OK ] $grp" + else + echo "[FAIL] $grp (Run: ujust dx-group)" + fi + done + + echo -e "\n4. Experience Layer (Bbrew Check):" + if command -v bbrew >/dev/null; then + echo "[ OK ] Bbrew is present." + else + echo "[WARN] Bbrew not found. Some uBlue workflows might be slower." + fi + +# Configure Bazzite-DX Curated Workstation Flavors +[group('System')] +bazzite-dx flavor="": + #!/usr/bin/env bash + set -e + FLAVOR="{{ flavor }}" + if [[ -z "$FLAVOR" ]]; then + echo "Choose a Bazzite-DX Workstation Flavor to activate (High-Fidelity):" + FLAVOR=$(gum choose "cli" "ai" "cncf" "all") + fi + case $FLAVOR in + cli) + echo "Aactivating: Terminal Excellence Bundle..." + brew bundle --file=/usr/share/ublue-os/homebrew/cli.Brewfile + ;; + ai) + echo "Activating: Agentic Excellence Bundle (Goose/Antigravity)..." + brew bundle --file=/usr/share/ublue-os/homebrew/ai-tools.Brewfile + ;; + cncf) + echo "Activating: Cloud-Native Standard (Kubernetes/Helm)..." + brew bundle --file=/usr/share/ublue-os/homebrew/cncf.Brewfile + ;; + all) + echo "Activating: Full uBlue Transformation..." + brew bundle --file=/usr/share/ublue-os/homebrew/cli.Brewfile + brew bundle --file=/usr/share/ublue-os/homebrew/ai-tools.Brewfile + brew bundle --file=/usr/share/ublue-os/homebrew/cncf.Brewfile + ;; + *) + echo "Unknown flavor: $FLAVOR" + exit 1 + ;; + esac + echo "Successfully enabled the $FLAVOR developer experience!" + +# Toggle between Bazzite and the Developer Experience (Atomic Rebase) +[group('System')] +toggle-devmode: + #!/usr/bin/env bash + set -e + IMAGE_INFO_FILE="/usr/share/ublue-os/image-info.json" + IMAGE_NAME="$(jq -rc '."image-name"' "${IMAGE_INFO_FILE}")" + IMAGE_REF="$(jq -rc '."image-ref"' "${IMAGE_INFO_FILE}" | sed "s/.*ghcr/ghcr/")" + CURRENT_IMAGE="${IMAGE_REF}:$(jq -rc '."image-tag"' "${IMAGE_INFO_FILE}")" + + if [[ "${IMAGE_NAME}" == *"dx"* ]]; then + gum confirm "Disable Developer Experience (Rebase to regular Bazzite)?" || exit 0 + NEW_IMAGE="$(echo ${CURRENT_IMAGE} | sed 's/-dx//')" + echo "Rebasing to: ${NEW_IMAGE}" + pkexec bootc switch --enforce-container-sigpolicy "${NEW_IMAGE}" + else + gum confirm "Enable Developer Experience (Transform into Bazzite-DX)?" || exit 0 + NEW_IMAGE="$(echo ${CURRENT_IMAGE} | sed 's/bazzite/bazzite-dx/')" + echo "Rebasing to: ${NEW_IMAGE}" + pkexec bootc switch --enforce-container-sigpolicy "${NEW_IMAGE}" + fi + +# Provision the current user for DX groups (Idempotent) +[group('System')] +dx-group: + #!/usr/bin/bash + set -euo pipefail + DX_GROUPS=("docker" "incus-admin" "libvirt" "dialout" "input" "video" "render" "plugdev") + echo "Syncing $USER with Developer Experience groups..." + for grp in "${DX_GROUPS[@]}"; do + if getent group "$grp" >/dev/null; then + if groups "$USER" | grep -q "\b$grp\b"; then + echo "Already sync'd: $grp" + else + sudo usermod -aG "$grp" "$USER" + echo "Sync'd: $grp" + fi + fi + done + echo "Done! Changes require a new session to take effect." + +# Monitor eBPF Performance (Requires sudo) +monitor-ebpf: + #!/usr/bin/bash + sudo bpftop diff --git a/build_files/60-clean-base.sh b/files/scripts/60-clean-base.sh similarity index 81% rename from build_files/60-clean-base.sh rename to files/scripts/60-clean-base.sh index f65abc89..aac1b67e 100755 --- a/build_files/60-clean-base.sh +++ b/files/scripts/60-clean-base.sh @@ -2,4 +2,4 @@ set -xeuo pipefail # Add bazzite-dx just file -echo "import \"/usr/share/ublue-os/just/95-bazzite-dx.just\"" >> /usr/share/ublue-os/justfile +echo "import \"/usr/share/ublue-os/just/95-bazzite-dx.just\"" >>/usr/share/ublue-os/justfile diff --git a/files/scripts/80-disable-repos.sh b/files/scripts/80-disable-repos.sh new file mode 100755 index 00000000..eede0ca7 --- /dev/null +++ b/files/scripts/80-disable-repos.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "::group:: ===$(basename "$0")===" + +echo "Disabling all repositories for final image..." + +# Use a loop to disable all repos in /etc/yum.repos.d/ +# This ensures compliance with the 'Build Firewall' (90-validate-repos.sh) +for repo in /etc/yum.repos.d/*.repo; do + if [ -f "$repo" ]; then + echo "Disabling $repo" + sed -i 's/enabled=1/enabled=0/g' "$repo" + fi +done + +echo "All repositories disabled." +echo "::endgroup::" diff --git a/files/scripts/90-validate-repos.sh b/files/scripts/90-validate-repos.sh new file mode 100755 index 00000000..0f361d81 --- /dev/null +++ b/files/scripts/90-validate-repos.sh @@ -0,0 +1,126 @@ +#!/usr/bin/bash + +echo "::group:: ===$(basename "$0")===" + +set -eou pipefail + +REPOS_DIR="/etc/yum.repos.d" +VALIDATION_FAILED=0 +ENABLED_REPOS=() + +echo "Validating all repository files are disabled..." + +# Check if repos directory exists +if [[ ! -d "$REPOS_DIR" ]]; then + echo "Warning: $REPOS_DIR does not exist" + exit 0 +fi + +# Function to check if a repo file has any enabled repos +check_repo_file() { + local repo_file="$1" + local basename_file + basename_file=$(basename "$repo_file") + + # Skip if file doesn't exist or isn't readable + [[ ! -f "$repo_file" ]] && return 0 + [[ ! -r "$repo_file" ]] && return 0 + + # Check for enabled=1 in the file + if grep -q "^enabled=1" "$repo_file" 2>/dev/null; then + echo "ENABLED: $basename_file" + ENABLED_REPOS+=("$basename_file") + VALIDATION_FAILED=1 + + # Show which sections are enabled + echo " Enabled sections:" + local section_name="" + while IFS= read -r line; do + if [[ "$line" =~ ^\[.*\]$ ]]; then + section_name="$line" + elif [[ "$line" =~ ^enabled=1 ]]; then + echo " - $section_name" + fi + done <"$repo_file" + else + echo "Disabled: $basename_file" + fi +} + +echo "" +echo "Checking COPR repositories (standard naming)..." +echo "NOTE: With secure isolated installation, NO COPRs should be globally enabled!" +# shellcheck disable=SC2016 +for repo in "$REPOS_DIR"/_copr:copr.fedorainfracloud.org:*.repo; do + [[ -f "$repo" ]] && check_repo_file "$repo" +done + +echo "" +echo "Checking COPR repositories (non-standard naming)..." +echo "SECURITY: Enabled COPRs can inject malicious versions of Fedora packages!" +for repo in "$REPOS_DIR"/_copr_*.repo; do + [[ -f "$repo" ]] && check_repo_file "$repo" +done + +echo "" +echo "Checking other third-party repositories..." +# List of known third-party repos that should be disabled +OTHER_REPOS=( + "negativo17-fedora-multimedia.repo" + "tailscale.repo" + "vscode.repo" + "docker-ce.repo" + "fedora-cisco-openh264.repo" + "fedora-coreos-pool.repo" + "terra.repo" + "fedora-surface.repo" + "supergfxctl.repo" + "asus-linux.repo" +) + +for repo_name in "${OTHER_REPOS[@]}"; do + repo_path="$REPOS_DIR/$repo_name" + if [[ -f "$repo_path" ]]; then + check_repo_file "$repo_path" + fi +done + +echo "" +echo "Checking RPM Fusion repositories..." +for repo in "$REPOS_DIR"/rpmfusion-*.repo; do + [[ -f "$repo" ]] && check_repo_file "$repo" +done + +echo "" +echo "Checking Fedora updates-testing (should be disabled unless beta)..." +if [[ -f "$REPOS_DIR/fedora-updates-testing.repo" ]]; then + if grep -q "^enabled=1" "$REPOS_DIR/fedora-updates-testing.repo" 2>/dev/null; then + # Allow updates-testing to be enabled for beta builds + if [[ "${UBLUE_IMAGE_TAG:-stable}" == "beta" ]]; then + echo "updates-testing is enabled (allowed for beta builds)" + else + echo "ENABLED: fedora-updates-testing.repo (should only be enabled for beta)" + ENABLED_REPOS+=("fedora-updates-testing.repo") + VALIDATION_FAILED=1 + fi + else + echo "Disabled: fedora-updates-testing.repo" + fi +fi + +# Final summary +echo "" +echo "======================================" +if [[ $VALIDATION_FAILED -eq 1 ]]; then + echo "VALIDATION FAILED" + echo "======================================" + echo "" + echo "The following repositories are still ENABLED:" + for repo in "${ENABLED_REPOS[@]}"; do + echo " ‱ $repo" + done + exit 1 +fi + +echo "All repository validation checks passed." +echo "::endgroup::" diff --git a/build_files/99-build-initramfs.sh b/files/scripts/99-build-initramfs.sh similarity index 68% rename from build_files/99-build-initramfs.sh rename to files/scripts/99-build-initramfs.sh index d5722ef5..28ed92a5 100755 --- a/build_files/99-build-initramfs.sh +++ b/files/scripts/99-build-initramfs.sh @@ -4,7 +4,7 @@ set -euo pipefail trap '[[ $BASH_COMMAND != echo* ]] && [[ $BASH_COMMAND != log* ]] && echo "+ $BASH_COMMAND"' DEBUG log() { - echo "=== $* ===" + echo "=== $* ===" } log "Building initramfs" @@ -12,13 +12,13 @@ log "Building initramfs" # Get kernel version and build initramfs KERNEL_VERSION="$(rpm -q --queryformat='%{evr}.%{arch}' kernel)" /usr/bin/dracut \ - --no-hostonly \ - --kver "$KERNEL_VERSION" \ - --reproducible \ - --zstd \ - -v \ - --add ostree \ - -f "/usr/lib/modules/$KERNEL_VERSION/initramfs.img" + --no-hostonly \ + --kver "$KERNEL_VERSION" \ + --reproducible \ + --zstd \ + -v \ + --add ostree \ + -f "/usr/lib/modules/$KERNEL_VERSION/initramfs.img" chmod 0600 "/usr/lib/modules/$KERNEL_VERSION/initramfs.img" diff --git a/files/scripts/999-cleanup.sh b/files/scripts/999-cleanup.sh new file mode 100755 index 00000000..83077a72 --- /dev/null +++ b/files/scripts/999-cleanup.sh @@ -0,0 +1,23 @@ +#!/usr/bin/bash +set -ouex pipefail + +echo "::group:: ===$(basename "$0")===" + +echo "Starting system cleanup..." + +# ── 1. Package Manager Cleanup +# Essential for keeping the final image size small +dnf5 clean all + +# ── 2. Safe Cache Cleanup +# We ONLY clean application-specific caches. +# We DO NOT touch /tmp/* or /var/tmp/* as they are often bind-mounted by BlueBuild. +rm -rf /var/log/dnf5.log || true + +# ── 3. Container Lint +# Validates the container image +bootc container lint || true + +echo "Cleanup completed" + +echo "::endgroup::" diff --git a/files/system/etc/libvirt/hooks/qemu b/files/system/etc/libvirt/hooks/qemu new file mode 100755 index 00000000..93dd7b9c --- /dev/null +++ b/files/system/etc/libvirt/hooks/qemu @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Author: SharkWipf +# +# Copy this file to /etc/libvirt/hooks, make sure it's called "qemu". +# After this file is installed, restart libvirt. +# From now on, you can easily add per-guest qemu hooks. +# Add your hooks in /etc/libvirt/hooks/qemu.d/vm_name/hook_name/state_name. +# For a list of available hooks, please refer to https://www.libvirt.org/hooks.html +# + +GUEST_NAME="$1" +HOOK_NAME="$2" +STATE_NAME="$3" +MISC="${@:4}" + +BASEDIR="$(dirname $0)" + +HOOKPATH="$BASEDIR/qemu.d/$GUEST_NAME/$HOOK_NAME/$STATE_NAME" + +STDIN=$(cat) # Buffer the entirety of stdin so we can pass it on to other scripts. Moderately bad idea, but if the Rust folks can do it so can I. + +set -e # If a script exits with an error, we should as well. + +# check if it's a non-empty executable file +if [ -f "$HOOKPATH" ] && [ -s "$HOOKPATH" ] && [ -x "$HOOKPATH" ]; then + eval \"$HOOKPATH\" "$@" <<< "$STDIN" # Call the hook with all our arguments and a copy of stdin. +elif [ -d "$HOOKPATH" ]; then + while read file; do + # check for null string + if [ ! -z "$file" ]; then + eval \"$file\" "$@" <<< "$STDIN" # Call each hook with all our arguments and a copy of stdin. + fi + done <<< "$(find -L "$HOOKPATH" -maxdepth 1 -type f -executable -print;)" +fi + diff --git a/files/system/etc/profile.d/90-bazzite-dx-starship.sh b/files/system/etc/profile.d/90-bazzite-dx-starship.sh new file mode 100644 index 00000000..f535172b --- /dev/null +++ b/files/system/etc/profile.d/90-bazzite-dx-starship.sh @@ -0,0 +1,11 @@ +# shellcheck shell=sh +# Bluefin-DX pattern: Initialize starship if present +command -v starship >/dev/null 2>&1 || return 0 + +if [ "$(basename "$(readlink /proc/$$/exe)")" = "bash" ]; then + eval "$(starship init bash)" +fi + +if [ "$(basename "$(readlink /proc/$$/exe)")" = "zsh" ]; then + eval "$(starship init zsh)" +fi diff --git a/files/system/etc/profile.d/95-bazzite-dx-bling.sh b/files/system/etc/profile.d/95-bazzite-dx-bling.sh new file mode 100644 index 00000000..5c993337 --- /dev/null +++ b/files/system/etc/profile.d/95-bazzite-dx-bling.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env sh + +# Bazzite-DX: Shell Excellence (Bling) +# Standardized Shell Experience for the uBlue Ecosystem. +# Supports both Bash and ZSH. + +# Prevent recursive sourcing (e.g. for atuin/pre-exec) +[ "${BLING_SOURCED:-0}" -eq 1 ] && return +BLING_SOURCED=1 + +# --- Premium Navigation & Aliases --- +if command -v eza >/dev/null; then + alias ls='eza --icons=auto --group-directories-first' + alias ll='eza -l --icons=auto --group-directories-first' + alias la='eza -a' + alias lt='eza --tree' +fi + +# --- Modern CLI Alternatives --- +if command -v ugrep >/dev/null; then + alias grep='ugrep' + alias egrep='ugrep -E' + alias fgrep='ugrep -F' +fi + +if command -v bat >/dev/null; then + alias cat='bat --style=plain --pager=never' +fi + +# --- Intelligent Shell Hooks --- +BLING_SHELL="$(basename "$(readlink /proc/$$/exe)")" + +# 1. Mise Activation (Tool Manager) +if command -v mise >/dev/null; then + eval "$(mise activate "${BLING_SHELL}")" +fi + +# 2. Direnv Activation (Directory Environment) +if command -v direnv >/dev/null; then + eval "$(direnv hook "${BLING_SHELL}")" +fi + +# 3. Zoxide Activation (Better 'cd') +if command -v zoxide >/dev/null; then + eval "$(zoxide init "${BLING_SHELL}")" +fi + +# 4. Starship Prompt (Final Prompt Logic) +if command -v starship >/dev/null; then + eval "$(starship init "${BLING_SHELL}")" +fi + +# 5. Atuin History Integration (Optional Sync) +# To enable cloud-native command history, run: atuin register +if command -v atuin >/dev/null; then + eval "$(atuin init "${BLING_SHELL}")" +fi + +# --- Power-User Extras --- +alias ..='cd ..' +alias ...='cd ../..' +alias ....='cd ../../..' +alias mkdir='mkdir -p' +alias g='git' +alias d='docker' +alias k='kubectl' diff --git a/system_files/etc/skel/.config/Code/User/settings.json b/files/system/etc/skel/.config/Code/User/settings.json similarity index 100% rename from system_files/etc/skel/.config/Code/User/settings.json rename to files/system/etc/skel/.config/Code/User/settings.json diff --git a/files/system/etc/systemd/resolved.conf.d/00-amyos-dns.conf b/files/system/etc/systemd/resolved.conf.d/00-amyos-dns.conf new file mode 100644 index 00000000..94ae8326 --- /dev/null +++ b/files/system/etc/systemd/resolved.conf.d/00-amyos-dns.conf @@ -0,0 +1,6 @@ +[Resolve] +Domains=~. +DNS=1.1.1.1#one.one.one.one 1.0.0.1#one.one.one.one 2606:4700:4700::1111#one.one.one.one 2606:4700:4700::1001#one.one.one.one 9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net +DNSOverTLS=opportunistic +DNSSEC=allow-downgrade +FallbackDNS=127.0.2.2 127.0.2.3 ::ffff:127.0.2.2 ::ffff:127.0.2.3 \ No newline at end of file diff --git a/system_files/etc/ublue-os/system_flatpaks b/files/system/etc/ublue-os/system_flatpaks similarity index 100% rename from system_files/etc/ublue-os/system_flatpaks rename to files/system/etc/ublue-os/system_flatpaks diff --git a/system_files/usr/bin/gamemode-nested b/files/system/usr/bin/gamemode-nested similarity index 100% rename from system_files/usr/bin/gamemode-nested rename to files/system/usr/bin/gamemode-nested diff --git a/files/system/usr/lib/NetworkManager/conf.d/40-bazzite-dx-privacy.conf b/files/system/usr/lib/NetworkManager/conf.d/40-bazzite-dx-privacy.conf new file mode 100644 index 00000000..31d86638 --- /dev/null +++ b/files/system/usr/lib/NetworkManager/conf.d/40-bazzite-dx-privacy.conf @@ -0,0 +1,7 @@ +[device] +wifi.scan-rand-mac-address=yes + +[connection] +wifi.cloned-mac-address=stable +ethernet.cloned-mac-address=stable +connection.stable-id=${CONNECTION}/${BOOT} \ No newline at end of file diff --git a/files/system/usr/lib/bootc/kargs.d/bazzite-dx.toml b/files/system/usr/lib/bootc/kargs.d/bazzite-dx.toml new file mode 100644 index 00000000..6e1433ab --- /dev/null +++ b/files/system/usr/lib/bootc/kargs.d/bazzite-dx.toml @@ -0,0 +1,12 @@ +# Bazzite-DX Specialized Kernel Arguments +# Focused on Virtualization performance and IOMMU for DX workloads. +# iommu=pt (Pass-Through) is the performance standard. +# Explicit intel/amd_iommu=on ensures coverage for diverse firmware (Handhelds/eGPUs). + +kargs = [ + "iommu=pt", + "intel_iommu=on", + "amd_iommu=on", + "kvm.nested=1", + "preempt=full" +] diff --git a/files/system/usr/lib/environment.d/95-bazzite-dx.conf b/files/system/usr/lib/environment.d/95-bazzite-dx.conf new file mode 100644 index 00000000..116a27f4 --- /dev/null +++ b/files/system/usr/lib/environment.d/95-bazzite-dx.conf @@ -0,0 +1,16 @@ +# Bazzite-DX Global Environment +# This ensures a "Zero-Conf" experience where developer tools are available +# in both interactive shells and GUI applications (IDE integration). + +# Homebrew/Linuxbrew Path Integration +PATH="/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:$PATH" + +# Homebrew Standard Variables +HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew" +HOMEBREW_CELLAR="/home/linuxbrew/.linuxbrew/Cellar" +HOMEBREW_REPOSITORY="/home/linuxbrew/.linuxbrew/Homebrew" + +# DX Defaults +EDITOR="nano" +VISUAL="nano" +PAGER="cat" diff --git a/files/system/usr/lib/environment.d/bazzite-dx.conf b/files/system/usr/lib/environment.d/bazzite-dx.conf new file mode 100644 index 00000000..b4b4c8cc --- /dev/null +++ b/files/system/usr/lib/environment.d/bazzite-dx.conf @@ -0,0 +1,9 @@ +# Bazzite-DX Global Environment Defaults +# These are baked into the image and apply to all users and sessions. +# Users can override these in ~/.config/environment.d/*.conf or /etc/environment.d/*.conf + +# Standard Developer Experience variables +EDITOR=nano +VISUAL=code +DX_VERSION=1.0 +BAZZITE_DX_LAYER=true diff --git a/files/system/usr/lib/modules-load.d/ip_tables.conf b/files/system/usr/lib/modules-load.d/ip_tables.conf new file mode 100644 index 00000000..e772a46d --- /dev/null +++ b/files/system/usr/lib/modules-load.d/ip_tables.conf @@ -0,0 +1 @@ +iptable_nat diff --git a/files/system/usr/lib/motd.d/30-bazzite-dx b/files/system/usr/lib/motd.d/30-bazzite-dx new file mode 100644 index 00000000..e4b39116 --- /dev/null +++ b/files/system/usr/lib/motd.d/30-bazzite-dx @@ -0,0 +1,18 @@ + + __ _ __ ______ _ __ + / /_ ____ _ ____ ____ (_) /_ ___ / __ / |/ / + / __ \ / __ `/|_ / |_ / / / __/ / _ \ / / / / / /|_/ / + / /_/ // /_/ / / /_ / /_ / / /_ / __/ / /_/ / / / / / + /_.___/ \__,_/ /___/ /___//_/\__/ \___/ /_____/ /_/ /_/ (Professional Edition) + + Welcome to Bazzite-DX! Your ultimate developer experience is ready. + + * Quick Actions: + - Status & Virtualization: ujust dx-status + - Install Extra Tools: ujust install-tools + - Documentation: https://dev.bazzite.gg + + * Support & Community: + - Discord / Discussions: https://bazzite.gg/community + + Happy Hacking! diff --git a/files/system/usr/lib/profile.d/bazzite-dx.sh b/files/system/usr/lib/profile.d/bazzite-dx.sh new file mode 100644 index 00000000..c82a7f1e --- /dev/null +++ b/files/system/usr/lib/profile.d/bazzite-dx.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash +# Bazzite-DX Shell Profile +# Fallback and interactive synchronization for developer tools. +# Ref: https://docs.projectbluefin.io/bluefin-dx/ + +if [ -d "/home/linuxbrew/.linuxbrew/bin" ]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" +fi + +# Set common DX productivity aliases +alias l='eza -lh' +alias ll='eza -lha' +alias cat='bat --paging=never' diff --git a/files/system/usr/lib/sysctl.d/60-bazzite-dx.conf b/files/system/usr/lib/sysctl.d/60-bazzite-dx.conf new file mode 100644 index 00000000..4234b543 --- /dev/null +++ b/files/system/usr/lib/sysctl.d/60-bazzite-dx.conf @@ -0,0 +1 @@ +net.ipv4.ip_forward = 1 diff --git a/files/system/usr/lib/sysctl.d/docker-ce.conf b/files/system/usr/lib/sysctl.d/docker-ce.conf new file mode 100644 index 00000000..4234b543 --- /dev/null +++ b/files/system/usr/lib/sysctl.d/docker-ce.conf @@ -0,0 +1 @@ +net.ipv4.ip_forward = 1 diff --git a/system_files/usr/lib/systemd/system/bazzite-dx-groups.service b/files/system/usr/lib/systemd/system/bazzite-dx-groups.service similarity index 56% rename from system_files/usr/lib/systemd/system/bazzite-dx-groups.service rename to files/system/usr/lib/systemd/system/bazzite-dx-groups.service index 64936f86..09e048ca 100644 --- a/system_files/usr/lib/systemd/system/bazzite-dx-groups.service +++ b/files/system/usr/lib/systemd/system/bazzite-dx-groups.service @@ -1,9 +1,12 @@ [Unit] Description=Add wheel members to docker, and incus-admin groups +After=local-fs.target +ConditionPathExists=!/etc/ublue/dx-groups [Service] Type=oneshot ExecStart=/usr/libexec/bazzite-dx-groups +RemainAfterExit=yes [Install] -WantedBy=default.target \ No newline at end of file +WantedBy=multi-user.target diff --git a/files/system/usr/lib/systemd/system/libvirt-workaround.service b/files/system/usr/lib/systemd/system/libvirt-workaround.service new file mode 100644 index 00000000..91498c76 --- /dev/null +++ b/files/system/usr/lib/systemd/system/libvirt-workaround.service @@ -0,0 +1,13 @@ +[Unit] +Description=Workaround to relabel libvirt files and directories +ConditionPathIsDirectory=/var/lib/libvirt/ +After=local-fs.target + +[Service] +Type=oneshot +ExecStart=-/usr/sbin/restorecon -R /var/log/libvirt/ +ExecStart=-/usr/sbin/restorecon -R /var/lib/libvirt/ +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/files/system/usr/lib/systemd/system/swtpm-workaround.service b/files/system/usr/lib/systemd/system/swtpm-workaround.service new file mode 100644 index 00000000..b4cd40d9 --- /dev/null +++ b/files/system/usr/lib/systemd/system/swtpm-workaround.service @@ -0,0 +1,20 @@ +[Unit] +Description=Workaround swtpm not having the correct label +ConditionFileIsExecutable=/usr/bin/swtpm +After=local-fs.target + +[Service] +Type=oneshot +# Copy if it doesn't exist +ExecStartPre=/usr/bin/bash -c "[ -x /usr/local/bin/overrides/swtpm ] || /usr/bin/cp /usr/bin/swtpm /usr/local/bin/overrides/swtpm" +# This is faster than using .mount unit. Also allows for the previous line/cleanup +ExecStartPre=/usr/bin/mount --bind /usr/local/bin/overrides/swtpm /usr/bin/swtpm +# Fix SELinux label +ExecStart=/usr/sbin/restorecon /usr/bin/swtpm +# Clean-up after ourselves +ExecStop=/usr/bin/umount /usr/bin/swtpm +ExecStop=/usr/bin/rm /usr/local/bin/overrides/swtpm +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/files/system/usr/lib/sysusers.d/bazzite-dx.conf b/files/system/usr/lib/sysusers.d/bazzite-dx.conf new file mode 100644 index 00000000..9b95bcf0 --- /dev/null +++ b/files/system/usr/lib/sysusers.d/bazzite-dx.conf @@ -0,0 +1,7 @@ +# Bazzite-DX Specialized Groups +# These are required for Virtualization, Containers, and Hardware access. + +g docker - - +g incus-admin - - +g libvirt - - +g adbusers - - diff --git a/files/system/usr/lib/tmpfiles.d/libvirt-workaround.conf b/files/system/usr/lib/tmpfiles.d/libvirt-workaround.conf new file mode 100644 index 00000000..861abeec --- /dev/null +++ b/files/system/usr/lib/tmpfiles.d/libvirt-workaround.conf @@ -0,0 +1,2 @@ +d /var/log/libvirt 0750 - - - - +L /var/lib/libvirt/qemu/networks/autostart/default.xml - - - - /usr/share/ublue-os/libvirt/default-network.xml diff --git a/files/system/usr/lib/tmpfiles.d/opt-fix.conf b/files/system/usr/lib/tmpfiles.d/opt-fix.conf new file mode 100644 index 00000000..418887f4 --- /dev/null +++ b/files/system/usr/lib/tmpfiles.d/opt-fix.conf @@ -0,0 +1 @@ +L+ /var/opt - - - - /usr/lib/opt diff --git a/files/system/usr/lib/tmpfiles.d/swtpm-workaround.conf b/files/system/usr/lib/tmpfiles.d/swtpm-workaround.conf new file mode 100644 index 00000000..62147f4e --- /dev/null +++ b/files/system/usr/lib/tmpfiles.d/swtpm-workaround.conf @@ -0,0 +1,2 @@ +C /usr/local/bin/overrides/swtpm - - - - /usr/bin/swtpm +d /var/lib/swtpm-localca 0750 tss tss - - diff --git a/files/system/usr/libexec/bazzite-dx-groups b/files/system/usr/libexec/bazzite-dx-groups new file mode 100644 index 00000000..daeca2db --- /dev/null +++ b/files/system/usr/libexec/bazzite-dx-groups @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +GROUP_SETUP_VER=1 +GROUP_SETUP_VER_FILE="/etc/ublue/dx-groups" +GROUP_SETUP_VER_RAN=$(cat "$GROUP_SETUP_VER_FILE" 2>/dev/null || echo "0") + +# make the directory if it doesn't exist +mkdir -p /etc/ublue + +# Run script if updated +if [[ -f $GROUP_SETUP_VER_FILE && "$GROUP_SETUP_VER" = "$GROUP_SETUP_VER_RAN" ]]; then + echo "Group setup has already run for version $GROUP_SETUP_VER. Exiting..." + exit 0 +fi + +# Function to append a group entry to /etc/group (Atomic /etc/group management) +append_group() { + local group_name="$1" + if ! grep -q "^$group_name:" /etc/group; then + echo "Appending $group_name to /etc/group" + grep "^$group_name:" /usr/lib/group | tee -a /etc/group >/dev/null + fi +} + +# Define the list of developer-essential groups +# These are synced with the 'ujust setup-dx-user' fallback for consistency. +DX_GROUPS=("docker" "podman" "libvirt" "incus-admin" "dialout" "input" "video" "render" "plugdev" "adbusers") + +# Ensure all target groups exist in /etc/group +for grp in "${DX_GROUPS[@]}"; do + append_group "$grp" +done + +# Provision all wheel users (administrators) with these groups +wheel_members=$(getent group wheel | cut -d ":" -f 4 | tr ',' ' ') +for user in $wheel_members; do + if id "$user" >/dev/null 2>&1; then + echo "Provisioning permissions for user: $user" + for grp in "${DX_GROUPS[@]}"; do + if getent group "$grp" >/dev/null; then + usermod -aG "$grp" "$user" + fi + done + fi +done + +# Prevent future executions for the same version +echo "Writing state file" +echo "$GROUP_SETUP_VER" >"$GROUP_SETUP_VER_FILE" diff --git a/files/system/usr/libexec/bazzite-dx-kvmfr-setup b/files/system/usr/libexec/bazzite-dx-kvmfr-setup new file mode 100644 index 00000000..8780e4d3 --- /dev/null +++ b/files/system/usr/libexec/bazzite-dx-kvmfr-setup @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +source /usr/lib/ujust/ujust.sh +# Get the image name based on the filename +IMAGE_NAME=$(basename "$0" | sed -E 's/(-dx|)-kvmfr-setup//') +if [ "$IMAGE_NAME" == "bazzite" ]; then + IMAGE_NAME="bazzite-dx" +fi + +# Required disclaimer and where to report issues first +echo "$(Urllink "https://looking-glass.io/docs/rc/ivshmem_kvmfr/#libvirt" "This module") along with $(Urllink "https://looking-glass.io" "Looking Glass") is very experimental and not recommended for production use!" +echo "The ublue team packages the kvmfr module only because it has to be supplied with the system image while using an atomic desktop." +echo "If you do plan to use Looking Glass, please $(Urllink "https://docs.bazzite.gg/Advanced/looking-glass/#compiling-looking-glass-client" "follow the guide here") on how to compile it for your system." +echo "To use the kvmfr module after enabling it, just add and edit the xml for libvirt from the documentation in the first link." +echo "Since we package the kvmfr module please open kvmfr related issues you have on ${IMAGE_NAME^} and tag @HikariKnight" +echo "in the $(Urllink "https://discord.gg/WEu6BdFEtp" "Universal Blue Discord") or the $(Urllink "https://github.com/ublue-os/${IMAGE_NAME,,}/issues" "${IMAGE_NAME^} Github issue tracker")." +echo "~ @HikariKnight" + +CONFIRM=$(Choose Ok Cancel) +if [ "$CONFIRM" == "Cancel" ]; then + exit 0 +fi + +# Add kvmfr modprobe file following upstream documentation +echo "Setting up kvmfr module so it loads next boot" +if [ -f "/etc/modprobe.d/kvmfr.conf" ]; then + echo "Re-creating dummy kvmfr modprobe file" + sudo rm /etc/modprobe.d/kvmfr.conf +fi +sudo bash -c 'cat << KVMFR_MODPROBE > /etc/modprobe.d/kvmfr.conf +# This is a dummy file and changing it does nothing +# If you want to change the kvmfr static_size_mb +# Run "rpm-ostree kargs --replace=kvmfr.static_size_mb=oldvalue=newvalue" +# Default value set by us is 128 which is enough for 4k SDR +# Find the current value by running "rpm-ostree kargs" +KVMFR_MODPROBE' + +# Add kvmfr static size karg +rpm-ostree kargs --append-if-missing="kvmfr.static_size_mb=128" + +# Add upstream udev rule for kvmfr, adjusted for fedora systems +echo "Adding udev rule for /dev/kvmfr0" +sudo bash -c 'cat << KVMFR_UDEV > /etc/udev/rules.d/99-kvmfr.rules +SUBSYSTEM=="kvmfr", GROUP="qemu", MODE="0660", TAG+="uaccess" +KVMFR_UDEV' + +# Add /dev/kvmfr0 to qemu cgroup device acl list +echo "Adding /dev/kvmfr0 to qemu cgroup_device_acl" +# This is not ideal and if someone has a better way to do this without perl, you are welcome to change it +sudo perl -0777 -pi -e 's/ +#cgroup_device_acl = \[ +# "\/dev\/null", "\/dev\/full", "\/dev\/zero", +# "\/dev\/random", "\/dev\/urandom", +# "\/dev\/ptmx", "\/dev\/kvm", +# "\/dev\/userfaultfd" +#\] +/ +cgroup_device_acl = \[ + "\/dev\/null", "\/dev\/full", "\/dev\/zero", + "\/dev\/random", "\/dev\/urandom", + "\/dev\/ptmx", "\/dev\/kvm", + "\/dev\/userfaultfd", "\/dev\/kvmfr0" +\] +/' /etc/libvirt/qemu.conf + +# Add SELinux context record for /dev/kvmfr0 (for simplicity we use the same one that was used for the shm) +echo "Adding SELinux context record for /dev/kvmfr0" +sudo semanage fcontext -a -t svirt_tmpfs_t /dev/kvmfr0 + +# Create type enforcement for /dev/kvmfr0 as there is no existing way to access kvmfr using virt context +echo "Adding SELinux access rules for /dev/kvmfr0" +if [ ! -d "$HOME/.config/selinux_te/mod" ]; then + mkdir -p "$HOME/.config/selinux_te/mod" +fi +if [ ! -d "$HOME/.config/selinux_te/pp" ]; then + mkdir -p "$HOME/.config/selinux_te/pp" +fi +if [ -f "$HOME/.config/selinux_te/kvmfr.te" ]; then + echo "Re-creating kvmfr selinux type enforcement rules" + rm $HOME/.config/selinux_te/kvmfr.te +fi +bash -c "cat << KVMFR_SELINUX > $HOME/.config/selinux_te/kvmfr.te +module kvmfr 1.0; +require { + type device_t; + type svirt_t; + class chr_file { open read write map }; +} +#============= svirt_t ============== +allow svirt_t device_t:chr_file { open read write map }; +KVMFR_SELINUX" + +# Tell user what type enforcement we made and how it looks like +echo "This is the type enforcement we wrote for SELinux and you can find it in $HOME/.config/selinux_te/kvmfr.te" +echo "#======= start of kvmfr.te =======" +cat "$HOME/.config/selinux_te/kvmfr.te" +echo "#======== end of kvmfr.te ========" +CONFIRM=$(Choose OK) + +# Convert .te a .pp file for semodule +checkmodule -M -m -o "$HOME/.config/selinux_te/mod/kvmfr.mod" "$HOME/.config/selinux_te/kvmfr.te" +semodule_package -o "$HOME/.config/selinux_te/pp/kvmfr.pp" -m "$HOME/.config/selinux_te/mod/kvmfr.mod" +sudo semodule -i "$HOME/.config/selinux_te/pp/kvmfr.pp" + +# Load kvmfr module into currently booted system +echo "Loading kvmfr module so you do not have to reboot to use it the first time" +sudo modprobe kvmfr static_size_mb=128 + +# Final message and regenerate initramfs so kvmfr loads next boot +echo "" +echo "Kvmfr0 $(Urllink "https://looking-glass.io/docs/rc/install_libvirt/#determining-memory" "static size is set to 128mb by default")" +echo "this will work with up to 4K SDR resolutiion, as most dummy plugs go up to 4K" +echo "some games will try use the adapters max resolution on first boot and cause issues if the value is too low." +echo "Most ghost display adapters max out at 4k, hence the default value of 128mb." +echo "" +echo "If you need to change it to a different value" +echo "you can read how to do that in /etc/modprobe.d/kvmfr.conf" +echo "$(Urllink "https://looking-glass.io/docs/rc/ivshmem_kvmfr/#libvirt" "Please read official documentation for kvmfr for how to use it")" +echo "" +if [ -e "/dev/kvmfr0" ]; then + sudo chown $USER:qemu /dev/kvmfr0 + echo "${b}NOTE: You can start using kvmfr right now without rebooting${n}" +else + echo "Please reboot in order to fully load the kvmfr module." +fi + +CONFIRM=$(Choose OK) \ No newline at end of file diff --git a/system_files/usr/share/applications/gamemode-nested.desktop b/files/system/usr/share/applications/gamemode-nested.desktop similarity index 100% rename from system_files/usr/share/applications/gamemode-nested.desktop rename to files/system/usr/share/applications/gamemode-nested.desktop diff --git a/system_files/usr/share/backgrounds/convergence.jxl b/files/system/usr/share/backgrounds/convergence.jxl similarity index 100% rename from system_files/usr/share/backgrounds/convergence.jxl rename to files/system/usr/share/backgrounds/convergence.jxl diff --git a/files/system/usr/share/dnf/plugins/copr.vendor.conf b/files/system/usr/share/dnf/plugins/copr.vendor.conf new file mode 100644 index 00000000..66b56177 --- /dev/null +++ b/files/system/usr/share/dnf/plugins/copr.vendor.conf @@ -0,0 +1,8 @@ +# This override is to handle the default behavior of dnf5 using ID at /etc/os-release +# to select which chroot gets used to fetch the copr repo. +# +# See: +# https://github.com/rpm-software-management/dnf5/blob/01d4df824ff4a94ae1fc288f81923d02ba71173a/dnf5-plugins/copr_plugin/copr_config.cpp#L79-L81 + +[main] +distribution = fedora diff --git a/files/system/usr/share/ublue-os/bazzite/fastfetch-dx.jsonc b/files/system/usr/share/ublue-os/bazzite/fastfetch-dx.jsonc new file mode 100644 index 00000000..5a3442ea --- /dev/null +++ b/files/system/usr/share/ublue-os/bazzite/fastfetch-dx.jsonc @@ -0,0 +1,104 @@ +{ + "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", + "logo": { + "type": "file", + "source": "/usr/share/ublue-os/bazzite/logo.txt", + "color": { + "1": "94", + "2": "47", + "3": "95", + "4": "0", + "5": "8", + "6": "55" + } + }, + "display": { + "separator": " îȘŸ ", + "color": { + "keys": "cyan" + } + }, + "modules": [ + { + "type": "title", + "key": " ", + "color": { + "user": "cyan", + "at": "white", + "host": "blue" + } + }, + "break", + { + "type": "os", + "key": " 󰣛", + "format": "{pretty-name} DX (Enterprise)" + }, + { + "type": "kernel", + "key": " ï…Œ", + "format": "{1} {2}" + }, + { + "type": "uptime", + "key": " 󰅐" + }, + "break", + { + "type": "host", + "key": " 󰟰" + }, + { + "type": "cpu", + "key": " ó°» " + }, + { + "type": "gpu", + "key": " 󰍛" + }, + { + "type": "memory", + "key": " " + }, + { + "type": "disk", + "key": " ", + "hideFS": "overlay" + }, + "break", + { + "type": "shell", + "key": " " + }, + { + "type": "terminal", + "key": " " + }, + { + "type": "packages", + "key": " 󰏖" + }, + "break", + { + "type": "command", + "key": " ", + "text": "docker --version | cut -d ' ' -f 3 | tr -d ','" + }, + { + "type": "command", + "key": " 󰡄", + "text": "podman --version | cut -d ' ' -f 3" + }, + { + "type": "command", + "key": " 󱄃", + "text": "virt-xml --version 2>/dev/null || echo 'N/A'" + }, + "break", + { + "type": "colors", + "paddingLeft": 2, + "symbol": "circle" + } + ] +} diff --git a/files/system/usr/share/ublue-os/homebrew/ai-tools.Brewfile b/files/system/usr/share/ublue-os/homebrew/ai-tools.Brewfile new file mode 100644 index 00000000..6dd4f492 --- /dev/null +++ b/files/system/usr/share/ublue-os/homebrew/ai-tools.Brewfile @@ -0,0 +1,26 @@ +# Bazzite-DX: Agentic Excellence (ai) +# A ready-to-code local AI ecosystem. + +# --- Taps (Trusted Sources) --- +tap "ublue-os/tap" +tap "ublue-os/experimental-tap" + +# --- Core AI Agents --- +brew "block-goose-cli" # Autonomous agent for filesystem/git ops +cask "ublue-os/tap/antigravity-linux" # The next-gen Linux agentic assistant +cask "ublue-os/tap/goose-linux" # Desktop edition of Goose +cask "claude-code" # Anthropic's official terminal agent + +# --- Local LLM & Model Servers --- +brew "ramalama" # Native OCI/Podman LLM manager +brew "ollama" # Local inference server (easy-mode) +cask "ublue-os/tap/lm-studio-linux" # Premium GUI for model management +flatpak "ai.jan.Jan" # The open source desktop LLM platform + +# --- AI CLI Utilities --- +brew "aichat" # Interactive chat in the terminal +brew "llm" # Streamlined CLI for OpenAI/Anthropic/Local +brew "llmfit" # Advanced quantization tools +brew "whisper-cpp" # High-performance speech-to-text +brew "gemini-cli" # Google's multimodal interface +brew "kimi-cli" # DeepSearch-capable CLI integration diff --git a/files/system/usr/share/ublue-os/homebrew/cli.Brewfile b/files/system/usr/share/ublue-os/homebrew/cli.Brewfile new file mode 100644 index 00000000..0b62c99f --- /dev/null +++ b/files/system/usr/share/ublue-os/homebrew/cli.Brewfile @@ -0,0 +1,19 @@ +# Bazzite-DX: Terminal Excellence (cli) +# Modern CLI Standard - Zero-Conf Productivity Tools. + +brew "bat" # Better cat with syntax highlighting +brew "eza" # Modern ls alternative +brew "fzf" # Command-line fuzzy finder +brew "zoxide" # Smarter cd command +brew "tealdeer" # Fast tldr implementation +brew "jq" # JSON command-line processor +brew "yq" # YAML command-line processor +brew "direnv" # Shell environment switcher +brew "glow" # Terminal markdown renderer +brew "duf" # Disk Usage/Free utility +brew "btop" # Interactive system monitor +brew "starship" # The customizable cross-shell prompt +brew "ripgrep" # Fast line-oriented search tool +brew "fd" # Simple, fast alternative to find +brew "atuin" # Magical shell history +brew "micro" # Intuitive terminal editor diff --git a/files/system/usr/share/ublue-os/homebrew/cncf.Brewfile b/files/system/usr/share/ublue-os/homebrew/cncf.Brewfile new file mode 100644 index 00000000..60d0c979 --- /dev/null +++ b/files/system/usr/share/ublue-os/homebrew/cncf.Brewfile @@ -0,0 +1,43 @@ +# Bazzite-DX: Cloud-Native Standard (cncf) +# Industrial Cloud Engineering - Graduated/Incubating Tools. + +# --- Taps (Trusted Sources) --- +tap "carvel-dev/carvel" +tap "k0sproject/tap" + +# --- Kubernetes Ecosystem (Graduated) --- +brew "kubernetes-cli" # Standard 'kubectl' CLI +brew "helm" # The Kubernetes package manager +brew "minikube" # Local dev cluster (multi-driver) +brew "kind" # Kubernetes in Docker (fast CI) +brew "argo" # Argo Workflows & Events +brew "argocd" # GitOps delivery tool +brew "flux" # GitOps automated delivery +brew "istioctl" # Service mesh management +brew "linkerd" # Lightweight service mesh +brew "opa" # Open Policy Agent +brew "prometheus" # Monitoring and alerting system + +# --- Kubernetes Dashboard & UX --- +brew "k9s" # Premium terminal UI for K8s +brew "cmctl" # Cert-manager control utility +brew "nerdctl" # Docker-compatible CLI for containerd + +# --- Cloud Infrastructure & Data (Incubating) --- +brew "crossplane" # Multicloud control plane +brew "lima" # Linux VMs for macOS/Linux workflows +brew "nats-server" # Cloud-native messaging +brew "notation" # Supply chain security (signing) +brew "thanos" # Long-term Prometheus storage + +# --- Carvel Toolset (Sandbox Excellence) --- +brew "carvel-dev/carvel/ytt" # YAML templating +brew "carvel-dev/carvel/kapp" # Kubernetes application deployment +brew "carvel-dev/carvel/kbld" # Image-to-deployment binding +brew "carvel-dev/carvel/imgpkg" # OCI artifact management + +# --- Local Cloud Runtimes --- +brew "k3d" # Lightweight k3s in Docker +brew "k0sproject/tap/k0sctl" # Zero-dependency K8s installer +flatpak "dev.k8slens.OpenLens" # Professional K8s IDE +flatpak "io.podman_desktop.PodmanDesktop" # Container desktop management diff --git a/files/system/usr/share/ublue-os/homebrew/dx-fonts.Brewfile b/files/system/usr/share/ublue-os/homebrew/dx-fonts.Brewfile new file mode 100644 index 00000000..ac151d06 --- /dev/null +++ b/files/system/usr/share/ublue-os/homebrew/dx-fonts.Brewfile @@ -0,0 +1,14 @@ +# Bazzite-DX: Extra Developer Fonts (fonts) +# Additional coding fonts available via Homebrew Casks. + +tap "homebrew/cask-fonts" + +# High-quality Monospace Fonts +cask "font-cascadia-code" +cask "font-monaspace" +cask "font-noto-sans-symbols" +cask "font-victor-mono" +cask "font-recursive" +cask "font-jetbrains-mono-nerd-font" +cask "font-hack-nerd-font" +cask "font-fira-code-nerd-font" diff --git a/files/system/usr/share/ublue-os/just/84-bazzite-virt.just b/files/system/usr/share/ublue-os/just/84-bazzite-virt.just new file mode 100644 index 00000000..02fc641b --- /dev/null +++ b/files/system/usr/share/ublue-os/just/84-bazzite-virt.just @@ -0,0 +1,178 @@ +# vim: set ft=make : + +# Setup and configure virtualization and vfio +setup-virtualization ACTION="": + #!/usr/bin/bash + source /usr/lib/ujust/ujust.sh + if [[ $(id -u) -eq 0 ]]; then + echo "Please do not run this command as root" + exit 1 + fi + # Check if we are running on a Steam Deck + if /usr/libexec/hwsupport/valve-hardware; then + echo "${red}${b}WARNING${n}: Virtualization is not properly supported on Steam Deck by Valve" + echo "Use at your own risk and performance may not be ideal." + fi + if [ "$(systemctl is-enabled libvirtd.service)" == "disabled" ]; then + echo "${b}libvirtd${n} service is ${red}disabled${n}!" + echo "${green}enabling${n} and starting libvirtd" + echo "If virt-manager says libvirtd.sock is not available after a big update, re-run this command." + sudo systemctl enable --now libvirtd 2> /dev/null + echo "Press ESC if you want to exit and do not need to do anything" + fi + OPTION={{ ACTION }} + if [ "$OPTION" == "help" ]; then + echo "Usage: ujust setup-virtualization