From 302bbf223958485fba53af33315d30adb000944d Mon Sep 17 00:00:00 2001 From: humocs-man <251756307+humocs-man@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:27:26 +0100 Subject: [PATCH] Initial commit --- .github/SETUP_CHECKLIST.md | 33 + .github/commit-convention.md | 116 +++ .github/copilot-instructions.md | 16 + .github/renovate.json5 | 63 ++ .github/workflows/build.yml | 218 ++++ .github/workflows/clean.yml | 27 + .github/workflows/renovate.yml | 31 + .github/workflows/validate-brewfiles.yml | 38 + .github/workflows/validate-flatpaks.yml | 37 + .github/workflows/validate-justfiles.yml | 40 + .github/workflows/validate-renovate.yml | 35 + .github/workflows/validate-shellcheck.yml | 37 + .gitignore | 4 + .pre-commit-config.yaml | 20 + AGENTS.md | 1108 +++++++++++++++++++++ CONTRIBUTING.md | 7 + Containerfile | 87 ++ Justfile | 318 ++++++ LICENSE | 201 ++++ README.md | 349 +++++++ artifacthub-repo.yml | 8 + build/10-build.sh | 65 ++ build/20-onepassword.sh.example | 68 ++ build/30-cosmic-desktop.sh.example | 92 ++ build/README.md | 77 ++ build/copr-helpers.sh | 30 + cosign.pub | 16 + custom/brew/README.md | 74 ++ custom/brew/default.Brewfile | 22 + custom/brew/development.Brewfile | 26 + custom/brew/fonts.Brewfile | 13 + custom/flatpaks/README.md | 95 ++ custom/flatpaks/default.preinstall | 114 +++ custom/ujust/README.md | 240 +++++ custom/ujust/custom-apps.just | 80 ++ custom/ujust/custom-system.just | 92 ++ iso/disk.toml | 3 + iso/iso.toml | 31 + iso/rclone/README.md | 128 +++ iso/rclone/aws-s3.conf | 29 + iso/rclone/backblaze-b2.conf | 24 + iso/rclone/cloudflare-r2.conf | 29 + iso/rclone/scp.conf | 29 + iso/rclone/sftp.conf | 30 + 44 files changed, 4200 insertions(+) create mode 100644 .github/SETUP_CHECKLIST.md create mode 100644 .github/commit-convention.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/renovate.json5 create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/clean.yml create mode 100644 .github/workflows/renovate.yml create mode 100644 .github/workflows/validate-brewfiles.yml create mode 100644 .github/workflows/validate-flatpaks.yml create mode 100644 .github/workflows/validate-justfiles.yml create mode 100644 .github/workflows/validate-renovate.yml create mode 100644 .github/workflows/validate-shellcheck.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100644 Containerfile create mode 100644 Justfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 artifacthub-repo.yml create mode 100755 build/10-build.sh create mode 100755 build/20-onepassword.sh.example create mode 100755 build/30-cosmic-desktop.sh.example create mode 100644 build/README.md create mode 100755 build/copr-helpers.sh create mode 100644 cosign.pub create mode 100644 custom/brew/README.md create mode 100644 custom/brew/default.Brewfile create mode 100644 custom/brew/development.Brewfile create mode 100644 custom/brew/fonts.Brewfile create mode 100644 custom/flatpaks/README.md create mode 100644 custom/flatpaks/default.preinstall create mode 100644 custom/ujust/README.md create mode 100644 custom/ujust/custom-apps.just create mode 100644 custom/ujust/custom-system.just create mode 100644 iso/disk.toml create mode 100644 iso/iso.toml create mode 100644 iso/rclone/README.md create mode 100644 iso/rclone/aws-s3.conf create mode 100644 iso/rclone/backblaze-b2.conf create mode 100644 iso/rclone/cloudflare-r2.conf create mode 100644 iso/rclone/scp.conf create mode 100644 iso/rclone/sftp.conf diff --git a/.github/SETUP_CHECKLIST.md b/.github/SETUP_CHECKLIST.md new file mode 100644 index 0000000..41fb465 --- /dev/null +++ b/.github/SETUP_CHECKLIST.md @@ -0,0 +1,33 @@ +# Repository Setup Checklist + +## Initial Setup + +### 1. Rename Template +- [ ] Update `finpilot` to your name in: Containerfile, Justfile, README.md, artifacthub-repo.yml + +### 2. Enable GitHub Actions +- [ ] Settings → Actions → General → Enable workflows +- [ ] Set "Read and write permissions" + +### 3. First Push +```bash +git add . +git commit -m "feat: initial customization" +git push origin main +``` + +### 4. Deploy +```bash +sudo bootc switch --transport registry ghcr.io/YOUR_USERNAME/YOUR_REPO:stable +sudo systemctl reboot +``` + +## Optional: Production Features + +### Enable Signing (Recommended) +```bash +cosign generate-key-pair +# Add cosign.key to GitHub Secrets as SIGNING_SECRET +# Uncomment signing in .github/workflows/build.yml +``` + diff --git a/.github/commit-convention.md b/.github/commit-convention.md new file mode 100644 index 0000000..0b4b7bd --- /dev/null +++ b/.github/commit-convention.md @@ -0,0 +1,116 @@ +# Conventional Commits for bootc Image Template + +This repository follows [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) specification. + +## Commit Message Format + +``` +(): + +[optional body] + +[optional footer(s)] +``` + +## Types + +Use these commit types for changes to this bootc image template: + +- **build**: Changes to the image build process (Containerfile, build.sh) +- **ci**: Changes to GitHub Actions workflows +- **config**: Changes to disk image configurations (TOML files) +- **feat**: New features or packages added to the image +- **fix**: Bug fixes or corrections +- **docs**: Documentation changes only +- **chore**: Maintenance tasks (dependencies, metadata) +- **refactor**: Code restructuring without changing behavior + +## Scopes + +Use these scopes to indicate what part of the image is affected: + +- **base**: Base image changes (FROM line in Containerfile) +- **packages**: Package installation/removal +- **services**: Systemd service configuration +- **disk**: Disk image configuration (ISO, QCOW2, RAW) +- **workflow**: GitHub Actions workflows +- **metadata**: Image metadata and branding +- **signing**: Cosign key and signing configuration + +## Examples + +### Adding Packages +``` +feat(packages): add development tools and editors + +- Install neovim, git, and htop +- Add Development Tools group +``` + +### Changing Base Image +``` +build(base): switch from Bazzite to Bluefin + +Changing to Bluefin for better developer tooling support +``` + +### Configuring Services +``` +feat(services): enable SSH and container services + +- Enable sshd.service for remote access +- Enable podman.socket for rootless containers +``` + +### ISO Configuration +``` +config(disk): update ISO to use custom registry + +Update bootc switch URL to point to ghcr.io/username/repo +``` + +### Workflow Changes +``` +ci(workflow): enable automatic ISO builds + +Configure build-disk.yml to trigger on main branch pushes +``` + +### Metadata Updates +``` +chore(metadata): update image description and keywords + +Update ArtifactHub metadata with project-specific information +``` + +### Documentation +``` +docs: add installation instructions for new features +``` + +### Bug Fixes +``` +fix(packages): disable COPR after package installation + +Ensure COPR repositories don't persist in final image +``` + +## Breaking Changes + +If a change is breaking (e.g., removing packages, changing base image significantly), add `!` after the type/scope: + +``` +build(base)!: migrate from Fedora 40 to Fedora 41 + +BREAKING CHANGE: This upgrade requires a clean reinstall for existing users. +``` + +## Tips + +- Keep subject line under 50 characters +- Use imperative mood ("add" not "added" or "adds") +- Don't end subject line with a period +- Separate subject from body with a blank line +- Use body to explain *what* and *why*, not *how* +- Be short and concise, avoid long commit messages, keep it simple +- Reference issues/PRs in footer when applicable diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ee732ad --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,16 @@ +# Copilot Instructions for finpilot + +This repository uses comprehensive agent instructions. Please refer to the main documentation file: + +**[AGENTS.md](../AGENTS.md)** in the repository root. + +This file contains: +- Repository structure and organization +- Build system details and workflows +- Package management guidelines +- Development best practices +- Validation and testing procedures +- Troubleshooting guides +- Attribution requirements for AI agents + +All coding agents should consult AGENTS.md for detailed instructions on working with this bootc image template. diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..21c572b --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,63 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices", + ], + + // Do not rebase when the default branch is updated + "rebaseWhen": "never", + + customManagers: [ + { + customType: 'regex', + description: 'Match container images in Justfile with tag and digest', + managerFilePatterns: [ + '^Justfile$', + ], + matchStrings: [ + '(?.+?)\\s:=\\s(?:env\\(.+?,\\s)?["\'](?\\S+?):(?\\S+?)@(?sha256:[a-f0-9]+?)["\']\\)?', + ], + datasourceTemplate: 'docker', + }, + { + customType: 'regex', + description: 'Match container images in Justfile run_args without tag or digest', + managerFilePatterns: [ + '^Justfile$', + ], + matchStrings: [ + 'run_args\\+=\\((?[a-zA-Z0-9._:/-]+):?(?\\S+?)\\)', + ], + datasourceTemplate: 'docker', + versioningTemplate: 'docker', + }, + ], + + "packageRules": [ + { + "automerge": true, + "matchUpdateTypes": ["pin", "pinDigest"] + }, + { + "description": "Auto-merge digest updates for base image and trigger build", + "automerge": true, + "matchManagers": ["dockerfile"], + "matchUpdateTypes": ["digest"], + "matchFileNames": ["Containerfile"], + "labels": ["renovate", "automerge"], + }, + { + "description": "Disable automerge for all rechunk updates to allow manual review", + "matchUpdateTypes": ["major", "minor", "patch", "digest", "pinDigest"], + "matchPackageNames": ["ghcr.io/hhd-dev/rechunk"], + "automerge": false + }, + { + "description": "Enable GitHub Actions updates since they are now pinned with SHA", + "enabled": true, + "matchUpdateTypes": ["digest", "pinDigest", "pin", "major", "minor", "patch"], + "matchDepTypes": ["action"], + "matchFileNames": [".github/workflows/**.yaml", ".github/workflows/**.yml"], + }, + ] +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..716b99d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,218 @@ +--- +name: Build container image +on: + pull_request: + branches: + - main + schedule: + - cron: '05 10 * * *' # 10:05am UTC everyday + push: + branches: + - main + paths-ignore: + - '**/README.md' + workflow_dispatch: + +env: + IMAGE_DESC: "My Customized Universal Blue Image" + IMAGE_KEYWORDS: "bootc,ublue,universal-blue" + IMAGE_LOGO_URL: "https://avatars.githubusercontent.com/u/120078124?s=200&v=4" # Put your own image here for a fancy profile on https://artifacthub.io/! + IMAGE_NAME: "${{ github.event.repository.name }}" # output image name, usually same as repo name + IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit + DEFAULT_TAG: "stable" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }}-${{ inputs.brand_name}}-${{ inputs.stream_name }} + cancel-in-progress: true + +jobs: + build_push: + name: Build and push image + runs-on: ubuntu-24.04 + + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Prepare environment + run: | + # Lowercase the image uri + echo "IMAGE_REGISTRY=${IMAGE_REGISTRY,,}" >> ${GITHUB_ENV} + echo "IMAGE_NAME=${IMAGE_NAME,,}" >> ${GITHUB_ENV} + + # These stage versions are pinned by https://github.com/renovatebot/renovate + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + # This is optional, but if you see that your builds are way too big for the runners, you can enable this by uncommenting the following lines: + # - name: Maximize build space + # uses: ublue-os/remove-unwanted-software@517622d6452028f266b7ba4cc9a123b5f58a6b53 # v7 + # with: + # remove-codeql: true + + - name: Mount BTRFS for podman storage + id: container-storage-action + uses: ublue-os/container-storage-action@911baca08baf30c8654933e9e9723cb399892140 # main + + # Fallback to the remove-unwanted-software-action if github doesn't allocate enough space + # See: https://github.com/ublue-os/container-storage-action/pull/11 + continue-on-error: true + with: + target-dir: /var/lib/containers + mount-opts: compress-force=zstd:2 + + - name: Get current date + id: date + run: | + # This generates a timestamp like what is defined on the ArtifactHub documentation + # E.G: 2022-02-08T15:38:15Z' + # https://artifacthub.io/docs/topics/repositories/container-images/ + # https://linux.die.net/man/1/date + echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" >> $GITHUB_OUTPUT + + # Image metadata for https://artifacthub.io/ - This is optional but is highly recommended so we all can get a index of all the custom images + # The metadata by itself is not going to do anything, you choose if you want your image to be on ArtifactHub or not. + - name: Image Metadata + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5 + id: metadata + with: + # This generates all the tags for your image, you can add custom tags here too! + # Default tags are "$DEFAULT_TAG" and "$DEFAULT_TAG.$date". + tags: | + type=raw,value=${{ env.DEFAULT_TAG }} + type=raw,value=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}} + type=raw,value={{date 'YYYYMMDD'}} + type=sha,enable=${{ github.event_name == 'pull_request' }} + type=ref,event=pr + labels: | + io.artifacthub.package.readme-url=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/${{ github.sha }}/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 }}/${{ github.sha }}/README.md + org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/blob/${{ github.sha }}/Containerfile + org.opencontainers.image.title=${{ env.IMAGE_NAME }} + org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/tree/${{ github.sha }} + org.opencontainers.image.vendor=${{ github.repository_owner }} + org.opencontainers.image.version=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}} + io.artifacthub.package.deprecated=false + io.artifacthub.package.keywords=${{ env.IMAGE_KEYWORDS }} + io.artifacthub.package.license=Apache-2.0 + io.artifacthub.package.logo-url=${{ env.IMAGE_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 + 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: ${{ env.DEFAULT_TAG }} + labels: ${{ steps.metadata.outputs.labels }} + oci: false + + # Tag for registry + - name: Tag for registry + run: | + for tag in ${{ steps.metadata.outputs.tags }}; do + podman tag ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} ${{ env.IMAGE_NAME }}:$tag + done + + # SBOM (OPTIONAL): Software Bill of Materials generation using Syft + # Creates a detailed inventory of all software components in the image for supply chain security. + # To enable: + # 1. Uncomment the "Setup Syft" step below + # 2. Uncomment the "Generate SBOM" step below + # 3. If using image signing, also uncomment the "Add SBOM Attestation" step further down + # Documentation: https://github.com/anchore/syft + + # - name: Setup Syft + # id: setup-syft + # uses: anchore/sbom-action/download-syft@8e94d75ddd33f69f691467e42275782e4bfefe84 # v0.20.9 + # with: + # syft-version: v1.20.0 + # + # - name: Generate SBOM + # id: generate-sbom + # env: + # IMAGE: ${{ env.IMAGE_NAME }} + # DEFAULT_TAG: ${{ env.DEFAULT_TAG }} + # SYFT_CMD: ${{ steps.setup-syft.outputs.cmd }} + # run: | + # OUTPUT_PATH="$(mktemp -d)/sbom.json" + # export SYFT_PARALLELISM=$(($(nproc)*2)) + # $SYFT_CMD ${IMAGE}:${DEFAULT_TAG} -o spdx-json=${OUTPUT_PATH} + # echo "OUTPUT_PATH=${OUTPUT_PATH}" >> $GITHUB_OUTPUT + + # 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@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push To GHCR + uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 + if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + id: push + env: + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ github.token }} + with: + registry: ${{ env.IMAGE_REGISTRY }} + image: ${{ env.IMAGE_NAME }} + tags: ${{ steps.metadata.outputs.tags }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + # OPTIONAL: Image Signing with Cosign + # Signing is disabled by default. To enable, see README.md "Optional: Enable Image Signing" section. + + # - name: Install Cosign + # uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + # + # - name: Sign container image + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + # run: | + # IMAGE_FULL="${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}" + # for tag in ${{ steps.metadata.outputs.tags }}; do + # cosign sign -y --key env://COSIGN_PRIVATE_KEY $IMAGE_FULL:$tag + # done + # env: + # TAGS: ${{ steps.push.outputs.digest }} + # COSIGN_EXPERIMENTAL: false + # COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} + + # OPTIONAL: SBOM Attestation + # Attaches SBOM (Software Bill of Materials) to your signed image for supply chain security. + # Requires image signing to be enabled first (see above). + # To enable: + # 1. First enable image signing (see above section) + # 2. Uncomment the "Add SBOM Attestation" step below + # Documentation: https://slsa.dev/spec/v1.0/requirements#provenance-available + + # - name: Add SBOM Attestation + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + # env: + # IMAGE: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }} + # DIGEST: ${{ steps.push.outputs.digest }} + # COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} + # SBOM_OUTPUT: ${{ steps.generate-sbom.outputs.OUTPUT_PATH }} + # run: | + # cd "$(dirname "$SBOM_OUTPUT")" + # cosign attest -y \ + # --predicate "$(basename "$SBOM_OUTPUT")" \ + # --type spdxjson \ + # --key env://COSIGN_PRIVATE_KEY \ + # "${IMAGE}@${DIGEST}" diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml new file mode 100644 index 0000000..921ec64 --- /dev/null +++ b/.github/workflows/clean.yml @@ -0,0 +1,27 @@ +name: Cleanup Old Images +on: + schedule: + - cron: "0 0 * * 0" # Weekly on Sunday at midnight UTC + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + +jobs: + delete-older-than-90: + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Delete Images Older Than 90 Days + uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4 # v1.0.16 + with: + token: ${{ secrets.GITHUB_TOKEN }} + # IMPORTANT: Update this to match your image name from Containerfile (line 9) + # This should match the "# Name: your-repo-name" comment in your Containerfile + # Example: If your Containerfile has "# Name: my-custom-os", use "my-custom-os" here + packages: finpilot + older-than: 90 days + delete-orphaned-images: true + keep-n-tagged: 7 + keep-n-untagged: 7 diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..070b203 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,31 @@ +name: Renovate +on: + schedule: + # Run every 6 hours + - cron: '0 */6 * * *' + workflow_dispatch: + push: + branches: + - main + paths: + - '.github/renovate.json5' + - '.github/workflows/renovate.yml' + +jobs: + renovate: + name: Run Renovate + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + - name: Self-hosted Renovate + uses: renovatebot/github-action@b9486682a19fe4ea2dd1263c11b4e0c1fe1838fe # v40.3.2 + with: + configurationFile: .github/renovate.json5 + token: ${{ secrets.GITHUB_TOKEN }} + env: + LOG_LEVEL: 'debug' diff --git a/.github/workflows/validate-brewfiles.yml b/.github/workflows/validate-brewfiles.yml new file mode 100644 index 0000000..56f35f4 --- /dev/null +++ b/.github/workflows/validate-brewfiles.yml @@ -0,0 +1,38 @@ +name: Validate Brewfiles +permissions: + contents: read + +on: + pull_request: + paths: + - "custom/brew/**" +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v5 + + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@1ccc07ccd54b6048295516a3eb89b192c35057dc # master + + - name: Validate Brewfiles + shell: bash + run: | + echo "Validating Brewfiles in custom/brew/ directory..." + + find "custom/brew" -iname '*\.Brewfile*' -print0 | \ + while IFS= read -r -d '' brewfile ; do \ + echo "::group:: ===$(basename $brewfile)===" + grep -E -e "^tap" $brewfile > taps.Brewfile || echo "# No taps" > taps.Brewfile + set -xeuo pipefail + brew bundle --file=./taps.Brewfile + if brew bundle exec whoami --file="$brewfile" | grep -F -e "${USER}" ; then + echo "✓ $brewfile is valid" + else + echo "✗ $brewfile validation failed" + exit 1 + fi + set +xeuo pipefail + echo "::endgroup::" + done diff --git a/.github/workflows/validate-flatpaks.yml b/.github/workflows/validate-flatpaks.yml new file mode 100644 index 0000000..3e3e5d9 --- /dev/null +++ b/.github/workflows/validate-flatpaks.yml @@ -0,0 +1,37 @@ +name: Validate Flatpaks +permissions: + contents: read + +on: + pull_request: + paths: + - "custom/flatpak/**" +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v5 + + - name: Set up Flathub + shell: bash + run: | + sudo apt install -y flatpak + flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo + + - name: Validate Flatpaks + shell: bash + run: | + echo "Validating flatpak files in custom/flatpak/ directory..." + + set -xeuo pipefail + + find "custom/flatpak" -iname '*\.list*' -print0 | \ + while IFS= read -r -d '' flatpaks_file ; do \ + echo "::group:: ===$(basename "$flatpaks_file")===" + grep -v "#.*" "$flatpaks_file" | grep -v "^$" | \ + while read -r flatpak ; do \ + flatpak remote-info --user flathub "${flatpak}" + done + echo "::endgroup::" + done diff --git a/.github/workflows/validate-justfiles.yml b/.github/workflows/validate-justfiles.yml new file mode 100644 index 0000000..348a10b --- /dev/null +++ b/.github/workflows/validate-justfiles.yml @@ -0,0 +1,40 @@ +name: Validate Justfiles +permissions: + contents: read + +on: + pull_request: + paths: + - "custom/ujust/**" + - "Justfile" +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v5 + + - name: Install just + uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2 + + - name: Validate Justfiles + shell: bash + run: | + echo "Validating justfiles in custom/ujust/ directory..." + + set -xeuo pipefail + + # Validate root Justfile + echo "::group:: Validating Justfile" + just --unstable --fmt --check -f Justfile + echo "::endgroup::" + + # Validate custom ujust files + find "custom/ujust" -iname '*.just' -print0 | \ + while IFS= read -r -d '' justfile ; do \ + echo "::group:: ===$(basename "$justfile")===" + just --unstable --fmt --check -f "$justfile" + echo "::endgroup::" + done + + echo "All justfiles are valid" diff --git a/.github/workflows/validate-renovate.yml b/.github/workflows/validate-renovate.yml new file mode 100644 index 0000000..2605f2a --- /dev/null +++ b/.github/workflows/validate-renovate.yml @@ -0,0 +1,35 @@ +name: Validate Renovate Config + +on: + pull_request: + paths: + - ".github/renovate.json5" + - ".github/workflows/renovate.yml" + push: + branches: + - main + paths: + - ".github/renovate.json5" + - ".github/workflows/renovate.yml" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v5 + + - name: Setup Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v6 + with: + node-version: latest + + - name: Install dependencies + shell: bash + env: + RENOVATE_VERSION: latest + run: npm install -g renovate@${RENOVATE_VERSION} + + - name: Validate Renovate config + shell: bash + run: renovate-config-validator --strict diff --git a/.github/workflows/validate-shellcheck.yml b/.github/workflows/validate-shellcheck.yml new file mode 100644 index 0000000..7a69341 --- /dev/null +++ b/.github/workflows/validate-shellcheck.yml @@ -0,0 +1,37 @@ +name: Validate Shell Scripts +permissions: + contents: read + +on: + pull_request: + paths: + - "build/**/*.sh" +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v5 + + - name: Install ShellCheck + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y shellcheck + + - name: Validate Shell Scripts + shell: bash + run: | + echo "Running shellcheck on build scripts..." + + set -xeuo pipefail + + find "build" -iname '*.sh' -print0 | \ + while IFS= read -r -d '' script ; do \ + echo "::group:: ===$(basename "$script")===" + shellcheck -x "$script" + echo "✓ $script passed shellcheck" + echo "::endgroup::" + done + + echo "All shell scripts passed validation" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9eee28b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +cosign.key +_build_* +output +_build-*/** diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c7dc0ec --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# .pre-commit-config.yaml + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-json + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: local + hooks: + - id: validate-brewfiles + name: Validate Brewfiles + entry: bash -c 'if command -v brew &> /dev/null; then for file in brew/*.Brewfile*; do [ -f "$file" ] || continue; brew bundle check --file="$file" || exit 1; done; else echo "⚠️ Homebrew not installed, skipping Brewfile validation"; fi' + language: system + files: '^brew/.*\.Brewfile.*$' + pass_filenames: false diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6c5908d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,1108 @@ +# Copilot Instructions for finpilot bootc Image Template + +## CRITICAL: GitHub API Usage + +**ALWAYS use GitHub API for external references:** +- When researching other repositories (e.g., projectbluefin/distroless, ublue-os/bluefin) +- When checking Containerfiles, build scripts, or configuration files +- Use the `github-mcp-server-get_file_contents` tool instead of curl/wget +- This ensures consistent, authenticated access and better error handling + +## CRITICAL: Pre-Commit Checklist + +**Execute before EVERY commit:** +1. **Conventional Commits** - ALL commits MUST follow conventional commit format (see below) +2. **Shellcheck** - `shellcheck *.sh` on all modified shell files +3. **YAML validation** - `python3 -c "import yaml; yaml.safe_load(open('file.yml'))"` on all modified YAML +4. **Justfile syntax** - `just --list` to verify +5. **Confirm with user** - Always confirm before committing and pushing + +**Never commit files with syntax errors.** + +### REQUIRED: Conventional Commit Format + +**ALL commits MUST use conventional commits format** + +``` +[optional scope]: +``` + +## CRITICAL: Template Initialization + +**When this repository is used as a template, you MUST:** + +### 1. Rename ALL instances of `finpilot` + +**Source of truth**: `Containerfile` line 9: `# Name: finpilot` + +**Files to update:** +- `Containerfile` (line 9) +- `Justfile` (line 1) +- `README.md` (line 1) +- `artifacthub-repo.yml` (line 5) +- `custom/ujust/README.md` (~line 175) +- `.github/workflows/ghcr-pruner.yml` (line 22) + +### 2. Create "What's Different" section in README + +**CRITICAL**: Add this section near the top of README.md (after the title/intro, before detailed docs): + +```markdown +## What Makes this Raptor Different? + +Here are the changes from [Base Image Name]. This image is based on [Bluefin/Bazzite/Aurora/etc] and includes these customizations: + +### Added Packages (Build-time) +- **System packages**: tmux, micro, mosh - [brief explanation of why] + +### Added Applications (Runtime) +- **CLI Tools (Homebrew)**: neovim, helix - [brief explanation] +- **GUI Apps (Flatpak)**: Spotify, Thunderbird - [brief explanation] + +### Removed/Disabled +- List anything removed from base image + +### Configuration Changes +- Any systemd services enabled/disabled +- Desktop environment changes +- Other notable modifications + +*Last updated: [date]* +``` + +**Maintenance requirement**: +- **ALWAYS update this section when you modify packages or configuration** +- Keep descriptions brief and user-focused (explain "why", not just "what") +- Write for typical Linux users, not developers +- Update the "Last updated" date with each change + +### 3. Verify copilot instructions are present + +Confirm `.github/copilot-instructions.md` exists in the new repository. This file should be automatically copied when using this as a GitHub template. + +### 4. Explain signing is optional + +Signing is DISABLED by default. First builds succeed immediately. Enable later for production (see README). + +**These 4 steps are REQUIRED for every new template instance.** + +--- + +## Repository Structure + +``` +├── Containerfile # Main build definition (multi-stage build with OCI imports) +├── Justfile # Local build automation (image name, build commands) +├── build/ # Build-time scripts (10-build.sh, 20-chrome.sh, etc.) +│ ├── 10-build.sh # Main build script (copy custom files, install packages) +│ ├── 20-*.sh.example # Example third-party repos (rename to use) +│ ├── 30-*.sh.example # Example desktop replacement (rename to use) +│ ├── copr-helpers.sh # Helper functions for COPR repositories +│ └── README.md # Build scripts documentation +├── custom/ # User customizations (NOT in container, installed at runtime/first boot) +│ ├── brew/ # Homebrew Brewfiles (CLI tools, dev tools) +│ │ ├── default.Brewfile # General CLI tools +│ │ ├── development.Brewfile # Dev environments +│ │ ├── fonts.Brewfile # Font packages +│ │ └── README.md # Homebrew documentation +│ ├── flatpaks/ # Flatpak preinstall (GUI apps, post-first-boot) +│ │ ├── default.preinstall # Default GUI apps (INI format) +│ │ └── README.md # Flatpak documentation +│ └── ujust/ # User commands (shortcuts to Brewfiles, system tasks) +│ ├── custom-apps.just # App installation shortcuts +│ ├── custom-system.just # System maintenance commands +│ └── README.md # ujust documentation +├── iso/ # Local testing only (no CI/CD) +│ ├── disk.toml # VM/disk image config (QCOW2/RAW) +│ ├── iso.toml # ISO installer config (bootc switch URL) +│ └── rclone/ # Upload configs (Cloudflare R2, AWS S3, etc.) +├── .github/ # GitHub configuration and CI/CD +│ ├── workflows/ # GitHub Actions workflows +│ │ ├── build.yml # Builds :stable on main +│ │ ├── clean.yml # Deletes images >90 days old +│ │ ├── renovate.yml # Renovate bot updates (6h interval) +│ │ ├── validate-*.yml # Pre-merge validation checks +│ │ └── ... +│ ├── copilot-instructions.md # THIS FILE - Instructions for Copilot +│ ├── SETUP_CHECKLIST.md # Quick setup checklist for users +│ ├── commit-convention.md # Conventional commits guide +│ └── renovate.json5 # Renovate configuration +├── .pre-commit-config.yaml # Pre-commit hooks (optional local use) +└── .gitignore # Prevents committing secrets (cosign.key, etc.) +``` + +--- + +## Core Principles + +### Multi-Stage Build Architecture +This template follows the **Bluefin architecture pattern** from @projectbluefin/distroless: + +**Architecture Layers:** +1. **Context Stage (ctx)** - Combines resources from multiple sources: + - Local build scripts (`/build`) + - Local custom files (`/custom`) + - **@projectbluefin/common** - Desktop configuration shared with Aurora (`/oci/common`) + - **@projectbluefin/branding** - Branding assets (`/oci/branding`) + - **@ublue-os/artwork** - Artwork shared with Aurora and Bazzite (`/oci/artwork`) + - **@ublue-os/brew** - Homebrew integration (`/oci/brew`) + +2. **Base Image Options:** + - `ghcr.io/ublue-os/silverblue-main:42` (Fedora-based, default) + - `quay.io/centos-bootc/centos-bootc:stream10` (CentOS-based) + +**OCI Container Resources:** +- Resources from OCI containers are copied to **distinct subdirectories** (`/oci/*`) to avoid file conflicts +- Renovate automatically updates `:latest` tags to **SHA digests** for reproducibility +- All OCI resources are mounted at build-time via the `ctx` stage + +**Reference:** See [Bluefin Contributing Guide](https://docs.projectbluefin.io/contributing/) for architecture diagram + +### Build-time vs Runtime +- **Build-time** (`build/`): Baked into container. Use `dnf5 install`. Services, configs, system packages. +- **Runtime** (`custom/`): User installs after deployment. Use Brewfiles, Flatpaks. CLI tools, GUI apps, dev environments. + +### Bluefin Convention Compliance +**ALWAYS follow @ublue-os/bluefin patterns. Confirm before deviating.** +- Use `dnf5` exclusively (never `dnf`, `yum`, `rpm-ostree`) +- Always `-y` flag for non-interactive +- COPRs: enable → install → **DISABLE** (critical, prevents repo persistence) +- Use `copr_install_isolated` function pattern +- Numbered scripts: `10-build.sh`, `20-chrome.sh`, `30-cosmic.sh` +- Check @bootc-dev for container best practices + +### Branch Strategy +- **main** = Production releases ONLY. Never push directly. Builds `:stable` images. +- **Conventional Commits** = REQUIRED. `feat:`, `fix:`, `chore:`, etc. +- **Workflows** = All validation happens on PRs. Merging to main triggers stable builds. + +### Validation Workflows +The repository includes automated validation on pull requests: +- **validate-shellcheck.yml** - Runs shellcheck on all `build/*.sh` scripts +- **validate-brewfiles.yml** - Validates Homebrew Brewfile syntax +- **validate-flatpaks.yml** - Checks Flatpak app IDs exist on Flathub +- **validate-justfiles.yml** - Validates just file syntax +- **validate-renovate.yml** - Validates Renovate configuration + +**When adding files**: These validations run automatically on PRs. Fix any errors before merge. + +--- + +## Where to Add Packages + +This section provides clear guidance on where to add different types of packages. + +### System Packages (dnf5 - Build-time) + +**Location**: `build/10-build.sh` + +System packages are installed at build-time and baked into the container image. Use `dnf5` exclusively. + +**Example**: +```bash +# In build/10-build.sh +dnf5 install -y vim git htop neovim tmux +``` + +**When to use**: +- System utilities and services +- Dependencies required for other build-time operations +- Packages that need to be available immediately on first boot +- Services that need to be enabled with `systemctl enable` + +**Important**: +- Always use `dnf5` (never `dnf`, `yum`, or `rpm-ostree`) +- Always add `-y` flag for non-interactive installs +- For COPR repositories, use `copr_install_isolated` pattern and disable after use +- For third-party repos, see example scripts: `build/20-onepassword.sh.example` + +**Script Naming Convention**: +- `10-build.sh` - Main build script (always runs first) +- `20-*.sh` - Additional scripts (run in numerical order) +- `30-*.sh` - Desktop environment changes +- `.example` suffix - Rename to `.sh` to activate + +### Homebrew Packages (Brew - Runtime) + +**Location**: `custom/brew/*.Brewfile` + +Homebrew packages are installed by users after deployment. Best for CLI tools and development environments. + +**Files**: +- `custom/brew/default.Brewfile` - General purpose CLI tools +- `custom/brew/development.Brewfile` - Development tools and environments +- `custom/brew/fonts.Brewfile` - Font packages +- Create custom `*.Brewfile` as needed + +**Example**: +```ruby +# In custom/brew/default.Brewfile +brew "bat" # cat with syntax highlighting +brew "eza" # Modern replacement for ls +brew "ripgrep" # Faster grep +brew "fd" # Simple alternative to find +``` + +**When to use**: +- CLI tools and utilities +- Development tools (node, python, go, etc.) +- User-specific tools that don't need to be in the base image +- Tools that update frequently + +**Important**: +- Brewfiles use Ruby syntax +- Users install via `ujust` commands (e.g., `ujust install-default-apps`) +- Not installed in ISO/container - users install after deployment + +### Flatpak Applications (GUI Apps - Runtime) + +**Location**: `custom/flatpaks/*.preinstall` + +Flatpak applications are GUI apps installed after first boot. Use INI format. + +**Files**: +- `custom/flatpaks/default.preinstall` - Default GUI applications +- Create custom `*.preinstall` files as needed + +**Example**: +```ini +# In custom/flatpaks/default.preinstall +[Flatpak Preinstall org.mozilla.firefox] +Branch=stable + +[Flatpak Preinstall com.visualstudio.code] +Branch=stable + +[Flatpak Preinstall org.gnome.Calculator] +Branch=stable +``` + +**When to use**: +- GUI applications +- Desktop apps (browsers, editors, media players) +- Apps that users expect to have immediately available +- Apps from Flathub (https://flathub.org/) + +**Important**: +- Installed post-first-boot (not in ISO/container) +- Requires internet connection +- Find app IDs at https://flathub.org/ +- Use INI format with `[Flatpak Preinstall APP_ID]` sections +- Always specify `Branch=stable` (or another branch) + +--- + +## Quick Reference: Common User Requests + +| Request | Action | Location | +|---------|--------|----------| +| Add package (build-time) | `dnf5 install -y pkg` | `build/10-build.sh` | +| Add package (runtime) | `brew "pkg"` | `custom/brew/default.Brewfile` | +| Add GUI app | `[Flatpak Preinstall org.app.id]` | `custom/flatpaks/default.preinstall` | +| Add user command | Create shortcut (NO dnf5) | `custom/ujust/*.just` | +| Add third-party repo | Use example scripts | `build/20-*.sh.example` (rename) | +| Replace desktop | Use example script | `build/30-cosmic-desktop.sh.example` | +| Switch base image | Update FROM line | `Containerfile` line 38 | +| Add OCI containers | Uncomment COPY --from= lines | `Containerfile` lines 13-18 (ctx stage) | +| Test locally | `just build && just build-qcow2 && just run-vm-qcow2` | Terminal | +| Deploy (production) | `sudo bootc switch ghcr.io/user/repo:stable` | Terminal | +| Enable service | `systemctl enable service.name` | `build/10-build.sh` | +| Add COPR | enable → install → **DISABLE** | `build/10-build.sh` | +| Validate changes | Automatic on PR | `.github/workflows/validate-*.yml` | + +--- + +## Detailed Workflows + +### 1. Multi-Stage Build Architecture + +**File**: `Containerfile` + +This template uses a **multi-stage build** following the @projectbluefin/distroless pattern. + +**Stage 1: Context (ctx) - Line 39** +Combines resources from multiple OCI containers: +```dockerfile +FROM scratch AS ctx + +COPY build /build +COPY custom /custom +# Import from OCI containers - Renovate updates :latest to SHA-256 digests +COPY --from=ghcr.io/ublue-os/base-main:latest /system_files /oci/base +COPY --from=ghcr.io/projectbluefin/common:latest /system_files /oci/common +COPY --from=ghcr.io/projectbluefin/branding:latest /system_files /oci/branding +COPY --from=ghcr.io/ublue-os/artwork:latest /system_files /oci/artwork +COPY --from=ghcr.io/ublue-os/brew:latest /system_files /oci/brew +``` + +**Stage 2: Base Image - Line 52** +```dockerfile +FROM ghcr.io/ublue-os/silverblue-main:latest # Default (Fedora-based) +# OR +FROM quay.io/centos-bootc/centos-bootc:stream10 # CentOS-based +``` + +**Common alternative base images**: +```dockerfile +FROM ghcr.io/ublue-os/bluefin:stable # Dev, GNOME, `:stable` or `:gts` +FROM ghcr.io/ublue-os/bazzite:stable # Gaming, Steam Deck +FROM ghcr.io/ublue-os/aurora:stable # KDE Plasma +FROM quay.io/fedora/fedora-bootc:42 # Upstream Fedora +``` + +**Tags**: `:stable` (recommended), `:latest` (bleeding edge), `-nvidia` variants available + +**Renovate**: Base image SHA and OCI container tags are auto-updated by Renovate bot every 6 hours (see `.github/renovate.json5`) + +**OCI Container Resources:** +- **@ublue-os/base-main** - Base system configuration +- **@projectbluefin/common** - Desktop configuration shared with Aurora +- **@projectbluefin/branding** - Branding assets +- **@ublue-os/artwork** - Artwork shared with Aurora and Bazzite +- **@ublue-os/brew** - Homebrew integration + +**File Locations in Build Scripts:** +- Local build scripts: `/ctx/build/` +- Local custom files: `/ctx/custom/` +- Base files: `/ctx/oci/base/` +- Common files: `/ctx/oci/common/` +- Branding files: `/ctx/oci/branding/` +- Artwork files: `/ctx/oci/artwork/` +- Brew files: `/ctx/oci/brew/` + +### 2. OCI Containers for Additional System Files + +**File**: `Containerfile` (ctx stage, lines 6-18) + +Following the `@projectbluefin/distroless` pattern, you can layer in additional system files from OCI containers. These are commented out by default in the template. + +**Available OCI Containers**: +```dockerfile +# Artwork and Branding from projectbluefin/common +COPY --from=ghcr.io/projectbluefin/common:latest /system_files/bluefin /files/bluefin +COPY --from=ghcr.io/projectbluefin/common:latest /system_files/shared /files/shared + +# Homebrew system files from ublue-os/brew +COPY --from=ghcr.io/ublue-os/brew:latest /system_files /files/brew +``` + +**What's included**: +- `projectbluefin/common:latest` - Bluefin wallpapers, themes, branding assets, ujust completions, udev rules +- `ublue-os/brew:latest` - Homebrew system integration files + +**When to use**: +- You want Bluefin-specific artwork and wallpapers in your custom image +- You want additional system integration beyond what the base image provides +- You're building a Bluefin derivative and want to maintain brand consistency + +**Important**: +- These are **commented out by default** as template examples +- Uncomment only if you specifically want these additional system files +- The files are copied into the `ctx` stage and made available to your build scripts +- To use the files in your build, you'll need to copy them from `/ctx/files/*` to appropriate system locations in your build scripts + +### 3. Build Scripts (`build/`) + +**Pattern**: Numbered files (`10-build.sh`, `20-chrome.sh`, `30-cosmic.sh`) run in order. + +**Example - `build/10-build.sh`**: +```bash +#!/usr/bin/env bash +set -euo pipefail + +# Install packages +dnf5 install -y vim git htop neovim + +# Enable services +systemctl enable podman.socket + +# Download binaries +curl -L https://example.com/tool -o /usr/local/bin/tool +chmod +x /usr/local/bin/tool +``` + +**Example - COPR pattern** (see `build/20-onepassword.sh`): +```bash +#!/usr/bin/env bash +set -euo pipefail + +source /ctx/copr-install-functions.sh + +# Chrome +dnf config-manager addrepo --from-repofile=https://dl.google.com/linux/linux_signing_key.pub +dnf5 install -y google-chrome-stable + +# 1Password via COPR (isolated) +copr_install_isolated username/repo package-name +``` + +**Example - Desktop swap** (see `build/30-cosmic.sh`): +```bash +#!/usr/bin/env bash +set -euo pipefail + +# Remove GNOME, install COSMIC +dnf5 group remove -y "GNOME Desktop Environment" +dnf5 copr enable -y ryanabx/cosmic-epoch +dnf5 install -y cosmic-desktop +dnf5 copr disable -y ryanabx/cosmic-epoch +systemctl set-default graphical.target +``` + +**CRITICAL**: Use `copr_install_isolated` function. Always disable COPRs. + +**Example scripts**: See `build/20-onepassword.sh.example` and `build/30-cosmic-desktop.sh.example` for complete working examples. + +### 4. Homebrew (`custom/brew/`) + +**Files**: `*.Brewfile` (Ruby syntax) + +**Example - `custom/brew/default.Brewfile`**: +```ruby +# CLI tools +brew "bat" # Better cat +brew "eza" # Better ls +brew "ripgrep" # Better grep +brew "fd" # Better find + +# Dev tools +tap "homebrew/cask" +brew "node" +brew "python" +``` + +**Users install via**: `ujust install-default-apps` (create shortcut in `custom/ujust/`) + +### 5. ujust Commands (`custom/ujust/`) + +**Files**: `*.just` (all auto-consolidated) + +**Example - `custom/ujust/apps.just`**: +```just +[group('Apps')] +install-default-apps: + #!/usr/bin/env bash + brew bundle --file /usr/share/ublue-os/homebrew/default.Brewfile + +[group('Apps')] +install-dev-tools: + #!/usr/bin/env bash + brew bundle --file /usr/share/ublue-os/homebrew/development.Brewfile +``` + +**RULES**: +- **NEVER** use `dnf5` in ujust - only Brewfile/Flatpak shortcuts +- Use `[group('Category')]` for organization +- All `.just` files merged during build + +### 6. Flatpaks (`custom/flatpaks/`) + +**Files**: `*.preinstall` (INI format, installed after first boot) + +**Example - `custom/flatpaks/default.preinstall`**: +```ini +[Flatpak Preinstall org.mozilla.firefox] +Branch=stable + +[Flatpak Preinstall org.gnome.Calculator] +Branch=stable + +[Flatpak Preinstall com.visualstudio.code] +Branch=stable +``` + +**Important**: Not in ISO/container. Installed post-first-boot. Requires internet. Find IDs at https://flathub.org/ + +### 7. ISO/Disk Images (`iso/`) + +**For local testing only. No CI/CD.** + +**Files**: +- `iso/disk.toml` - VM images (QCOW2/RAW): `just build-qcow2` +- `iso/iso.toml` - Installer ISO: `just build-iso` + +**CRITICAL** - Update bootc switch URL in `iso/iso.toml`: +```toml +[customizations.installer.kickstart] +contents = """ +%post +bootc switch --mutate-in-place --transport registry ghcr.io/USERNAME/REPO:stable +%end +""" +``` + +**Upload**: Use `iso/rclone/` configs (Cloudflare R2, AWS S3, Backblaze B2, SFTP) + +### 8. Release Workflow + +**Branches**: +- `main` - Production only. Builds `:stable` images. Never push directly. + +**Workflows**: +- `build.yml` - Builds `:stable` on main +- `renovate.yml` - Monitors base image updates (every 6 hours) +- `clean.yml` - Deletes images >90 days (weekly) +- `validate-*.yml` - Pre-merge validation (shellcheck, Brewfile, Flatpak, etc.) + +**Image Tags**: +- `:stable` - Latest stable release from main branch +- `:stable.YYYYMMDD` - Datestamped stable release +- `:YYYYMMDD` - Date only +- `:pr-123` - Pull request builds (for testing) +- `:sha-abc123` - Git commit SHA (short) + +**Renovate Bot**: +- Automatically updates base image SHAs in `Containerfile` +- Runs every 6 hours (configured in `.github/renovate.json5`) +- Creates PRs for updates - review and merge to keep images current + +### 8. Understanding the Multi-Stage Build Architecture + +This template implements a **multi-stage build pattern** following @projectbluefin/distroless. + +**Why Multi-Stage?** +- **Modularity**: Combine resources from multiple OCI containers +- **Reusability**: Share common components across different images +- **Maintainability**: Update shared components independently +- **Reproducibility**: Renovate updates OCI container tags to SHA digests + +**Stage Breakdown:** + +**Stage 1: Context (ctx)** +```dockerfile +FROM scratch AS ctx +COPY build /build # Local build scripts +COPY custom /custom # Local customizations +COPY --from=ghcr.io/projectbluefin/common:latest /system_files /oci/common +COPY --from=ghcr.io/projectbluefin/branding:latest /system_files /oci/branding +COPY --from=ghcr.io/ublue-os/artwork:latest /system_files /oci/artwork +COPY --from=ghcr.io/ublue-os/brew:latest /system_files /oci/brew +``` + +This stage combines: +- **Local resources** (build scripts, custom files) +- **OCI container resources** from upstream projects +- Resources are copied to **distinct subdirectories** to avoid conflicts + +**Stage 2: Final Image** +```dockerfile +FROM ghcr.io/ublue-os/silverblue-main:42 + +RUN --mount=type=bind,from=ctx,source=/,target=/ctx \ + /ctx/build/10-build.sh +``` + +The final stage: +- Starts from base image +- Mounts the `ctx` stage at `/ctx` +- Runs build scripts with access to all resources + +**Accessing OCI Resources in Build Scripts:** + +Build scripts can access files from OCI containers: +```bash +#!/usr/bin/env bash +# Example: Copy branding files +cp -r /ctx/oci/branding/* /usr/share/branding/ + +# Example: Copy common desktop config +cp /ctx/oci/common/config.yaml /etc/myapp/ + +# Example: Use brew files +cp /ctx/oci/brew/*.sh /usr/local/bin/ +``` + +**Renovate Integration:** +- Renovate monitors OCI container tags (`:latest`) +- Automatically updates to SHA digests for reproducibility +- Example: `:latest` → `@sha256:abc123...` +- Ensures builds are reproducible and verifiable + +**Reference:** See [Bluefin Contributing Guide](https://docs.projectbluefin.io/contributing/) for architecture diagram + +### 9. Image Signing (Optional, Recommended for Production) + +**Default**: DISABLED (commented out in workflows) to allow first builds. +```bash +# Generate keys +COSIGN_PASSWORD="" cosign generate-key-pair +# Creates: cosign.key (SECRET), cosign.pub (COMMIT) + +# Add to GitHub +# Settings → Secrets and Variables → Actions → New secret +# Name: SIGNING_SECRET +# Value: + +# Uncomment signing sections in: +# - .github/workflows/build.yml +# - .github/workflows/build-testing.yml +``` + +**NEVER commit `cosign.key`**. Already in `.gitignore`. + +--- + +## Critical Rules (Enforced) + +1. **ALWAYS** use Conventional Commits format for ALL commits (required for Release Please) + - Format: `[scope]: ` + - Valid types: `feat:`, `fix:`, `docs:`, `chore:`, `build:`, `ci:`, `refactor:`, `test:` + - Breaking changes: Add `!` or `BREAKING CHANGE:` in footer + - See `.github/commit-convention.md` for examples +2. **NEVER** commit `cosign.key` to repository +3. **ALWAYS** disable COPRs after use (`copr_install_isolated` function) +4. **ALWAYS** use `dnf5` exclusively (never `dnf`, `yum`, `rpm-ostree`) +5. **ALWAYS** use `-y` flag for non-interactive installs +6. **NEVER** use `dnf5` in ujust files - only Brewfile/Flatpak shortcuts +7. **ALWAYS** work on testing branch for development +8. **ALWAYS** let Release Please handle testing→main merges +9. **NEVER** push directly to main (only via Release Please) +10. **ALWAYS** confirm with user before deviating from @ublue-os/bluefin patterns +11. **ALWAYS** run shellcheck/YAML validation before committing +12. **ALWAYS** update bootc switch URL in `iso/iso.toml` to match user's repo +13. **ALWAYS** follow numbered script convention: `10-*.sh`, `20-*.sh`, `30-*.sh` +14. **ALWAYS** check example scripts before creating new patterns (`.example` files in `build/`) +15. **ALWAYS** validate that new Flatpak IDs exist on Flathub before adding +16. **NEVER** modify validation workflows without understanding impact on PR checks +--- + +## Troubleshooting + +| Symptom | Cause | Solution | +|---------|-------|----------| +| Build fails: "permission denied" | Signing misconfigured | Verify signing commented out OR `SIGNING_SECRET` set | +| Build fails: "package not found" | Typo or unavailable | Check spelling, verify on RPMfusion, add COPR if needed | +| Build fails: "base image not found" | Invalid FROM line | Check syntax in `Containerfile` line 24 | +| Build fails: "shellcheck error" | Script syntax error | Run `shellcheck build/*.sh` locally, fix errors | +| PR validation fails: Brewfile | Invalid Brewfile syntax | Check Ruby syntax, ensure packages exist | +| PR validation fails: Flatpak | Invalid app ID | Verify app ID exists on https://flathub.org/ | +| PR validation fails: justfile | Invalid just syntax | Run `just --list` locally to test | +| Changes not in production | Wrong workflow | Push to main (via PR) to trigger stable builds | +| ISO missing customizations | Wrong bootc URL | Update `iso/iso.toml` bootc switch URL to match repo | +| COPR packages missing after boot | COPR not disabled | COPRs persist if not disabled - use `copr_install_isolated` | +| ujust commands not working | Wrong install location | Files must be in `custom/ujust/` and copied to `/usr/share/ublue-os/just/` | +| Flatpaks not installed | Expected behavior | Flatpaks install post-first-boot, not in ISO/container | +| Local build fails | Wrong environment | Must run on bootc-based system or have podman installed | +| Renovate not creating PRs | Configuration issue | Check `.github/renovate.json5` syntax | +| Third-party repo not working | Repo file persists | Remove repo file at end of script (see examples) | + +--- + +## Common Patterns & Examples + +### Pattern 1: Adding Third-Party RPM Repositories + +**Use case**: Installing Google Chrome, 1Password, VS Code, etc. + +**Example**: See `build/20-onepassword.sh.example` + +**Steps**: +1. Add GPG key (if required) +2. Create repo file in `/etc/yum.repos.d/` +3. Install packages with `dnf5 install -y` +4. **CRITICAL**: Remove repo file at end + +```bash +# Add repo +cat > /etc/yum.repos.d/google-chrome.repo << 'EOF' +[google-chrome] +name=google-chrome +baseurl=https://dl.google.com/linux/chrome/rpm/stable/x86_64 +enabled=1 +gpgcheck=1 +gpgkey=https://dl.google.com/linux/linux_signing_key.pub +EOF + +# Install +dnf5 install -y google-chrome-stable + +# Clean up (required!) +rm -f /etc/yum.repos.d/google-chrome.repo +``` + +### Pattern 2: Using COPR Repositories + +**Use case**: Installing packages from Fedora COPR (community repos) + +**Example**: See `build/copr-helpers.sh` and `build/30-cosmic-desktop.sh.example` + +**Always use `copr_install_isolated` function**: +```bash +source /ctx/build/copr-helpers.sh + +# Install from COPR (isolated - auto-disables after install) +copr_install_isolated "ublue-os/staging" package-name + +# Install multiple packages +copr_install_isolated "ryanabx/cosmic-epoch" \ + cosmic-session \ + cosmic-greeter \ + cosmic-comp +``` + +### Pattern 3: Replacing Desktop Environment + +**Use case**: Swap GNOME for KDE, COSMIC, etc. + +**Example**: See `build/30-cosmic-desktop.sh.example` + +**Steps**: +1. Remove old desktop: `dnf5 remove -y gnome-shell ...` +2. Install new desktop: `copr_install_isolated ...` +3. Configure display manager: `systemctl enable ...` +4. Set default session + +### Pattern 4: Enabling System Services + +**Location**: `build/10-build.sh` + +```bash +# Enable service +systemctl enable podman.socket + +# Mask unwanted service +systemctl mask unwanted-service + +# Set default target +systemctl set-default graphical.target +``` + +### Pattern 5: Creating Custom ujust Commands + +**Location**: `custom/ujust/*.just` + +**Example structure**: +```just +# vim: set ft=make : + +# Install development tools +[group('Apps')] +install-dev-tools: + #!/usr/bin/env bash + echo "Installing development tools..." + brew bundle --file /usr/share/ublue-os/homebrew/development.Brewfile + +# Custom system command +[group('System')] +my-custom-command: + #!/usr/bin/env bash + echo "Running custom command..." + # Your logic here (NO dnf5!) +``` + +### Pattern 6: Local Testing Workflow + +**Complete local testing cycle**: +```bash +# 1. Build container image +just build + +# 2. Build QCOW2 disk image +just build-qcow2 + +# 3. Run in VM +just run-vm-qcow2 + +# Or combine all steps +just build && just build-qcow2 && just run-vm-qcow2 +``` + +**Alternative**: Build ISO for installation testing +```bash +just build +just build-iso +just run-vm-iso +``` + +### Pattern 7: Pre-commit Validation (Optional) + +**Setup pre-commit hooks locally**: +```bash +# Install pre-commit +pip install pre-commit + +# Install hooks +pre-commit install + +# Run manually +pre-commit run --all-files +``` + +**Note**: Pre-commit config exists (`.pre-commit-config.yaml`) but is optional. CI validation runs automatically on PRs. + +--- + +## Advanced Topics + +### /opt Immutability +Some packages (Chrome, Docker Desktop) write to `/opt`. On Fedora, it's symlinked to `/var/opt` (mutable). To make immutable: + +Uncomment `Containerfile` line 20: +```dockerfile +RUN rm /opt && mkdir /opt +``` + +### Multi-Architecture +- Local `just` commands support your platform +- Most UBlue images support amd64/arm64 +- Add `-arm64` suffix if needed: `bluefin-arm64:stable` +- Cross-platform builds require additional setup + +### Custom Build Functions +See `build/copr-install-functions.sh` for reusable patterns: +- `copr_install_isolated` - Enable COPR, install packages, disable COPR +- Follow @ublue-os/bluefin conventions exactly + + + +--- + +## Understanding the Build Process + +### Container Build Flow + +1. **Base Image** - Pulls base image specified in `Containerfile` FROM line +2. **Context Stage** - Mounts `build/` and `custom/` directories +3. **Build Scripts** - Runs scripts in `build/` directory in numerical order: + - `10-build.sh` - Always runs first (copies custom files, installs packages) + - `20-*.sh` - Additional scripts (if present and not .example) + - `30-*.sh` - More scripts (if present and not .example) +4. **Container Lint** - Validates final image with `bootc container lint` +5. **Push to Registry** - Uploads to GitHub Container Registry (ghcr.io) + +### What Gets Included in the Image + +**Build-time (baked into image)**: +- System packages from `dnf5 install` +- Enabled systemd services +- Custom files copied from `/ctx/custom/` to standard locations: + - Brewfiles → `/usr/share/ublue-os/homebrew/` + - ujust files → `/usr/share/ublue-os/just/60-custom.just` + - Flatpak preinstall → `/etc/flatpak/preinstall.d/` + +**Runtime (installed after deployment)**: +- Homebrew packages (user runs `ujust install-*`) +- Flatpak applications (installed on first boot, requires internet) + +### Local vs CI Builds + +**Local builds** (with `just build`): +- Uses your local podman +- Faster for testing +- No signing +- No automatic push to registry + +**CI builds** (GitHub Actions): +- Uses GitHub runners +- Automatic on push/PR +- Includes validation steps +- Can include signing +- Automatic push to ghcr.io + +### Image Layers and Caching + +**Efficient layering**: +- Each `RUN` command creates a new layer +- Layers are cached between builds +- Changes near end of Containerfile = faster rebuilds +- Use `--mount=type=cache` for package managers + +**Best practices**: +- Group related `dnf5 install` commands together +- Don't install and remove in same layer +- Clean up in same RUN command as install + +--- + +## Image Tags Reference + +**Main branch** (production releases): +- `stable` - Latest stable release (recommended) +- `stable.20250129` - Datestamped stable release +- `20250129` - Date only +- `v1.0.0` - Version from Release Please + +**PR builds**: +- `pr-123` - Pull request number +- `sha-abc123` - Git commit SHA (short) + +--- + +## File Modification Priority + +When user requests customization, check in this order: + +1. **`build/10-build.sh`** (50%) - Build-time packages, services, system configs +2. **`custom/brew/`** (20%) - Runtime CLI tools, dev environments +3. **`custom/ujust/`** (15%) - User convenience commands +4. **`custom/flatpaks/`** (5%) - GUI applications +5. **`Containerfile`** (5%) - Base image, /opt config, advanced builds +6. **`Justfile`** (2%) - Image name, build parameters +7. **`iso/*.toml`** (2%) - ISO/disk customization for testing +8. **`.github/workflows/`** (1%) - Metadata, triggers, workflow config + +### Files to AVOID Modifying + +**Do NOT modify unless specifically requested or necessary**: +- `.github/renovate.json5` - Renovate configuration (auto-updates) +- `.github/workflows/validate-*.yml` - Validation workflows +- `.gitignore` - Prevents committing secrets +- `build/copr-helpers.sh` - Helper functions (stable patterns) +- `LICENSE` - Repository license +- `cosign.pub` - Public signing key (regenerate if changing keys) + +**Modify with extreme caution**: +- `.github/workflows/build.yml` - Core build workflow +- `.github/workflows/clean.yml` - Image cleanup +- `Justfile` - Local build automation (users rely on these commands) + +--- + +## Debugging Tips + +### Local Debugging + +**Build failures**: +```bash +# Build with verbose output +podman build --log-level=debug . + +# Check build script syntax +shellcheck build/*.sh + +# Test specific script in container +podman run --rm -it ghcr.io/ublue-os/bluefin:stable bash +# Then run your script commands manually +``` + +**Brewfile issues**: +```bash +# Validate Brewfile syntax +brew bundle check --file custom/brew/default.Brewfile + +# List what would be installed +brew bundle list --file custom/brew/default.Brewfile +``` + +**Just file issues**: +```bash +# Check syntax +just --list + +# Check specific file +just --unstable --fmt --check -f custom/ujust/custom-apps.just + +# Run specific command with debug +just --verbose install-default-apps +``` + +### CI Debugging + +**Check workflow logs**: +1. Go to Actions tab in GitHub +2. Click on failed workflow run +3. Expand failed step +4. Look for error messages + +**Common CI failures**: +- Shellcheck errors: Fix script syntax +- Brewfile validation: Check package names exist +- Flatpak validation: Verify app IDs on Flathub +- Image pull failures: Check base image SHA/tag + +**Test PR before merge**: +```bash +# PR builds are tagged as :pr-NUMBER +podman pull ghcr.io/YOUR_USERNAME/YOUR_REPO:pr-123 +podman run --rm -it ghcr.io/YOUR_USERNAME/YOUR_REPO:pr-123 bash +``` + +### Runtime Debugging + +**After deployment**: +```bash +# Check system info +bootc status + +# Check running services +systemctl list-units --failed + +# Check logs +journalctl -b -p err + +# Check ujust commands available +ujust --list + +# Check Brewfiles location +ls -la /usr/share/ublue-os/homebrew/ + +# Check Flatpak preinstall +ls -la /etc/flatpak/preinstall.d/ +``` + +**Flatpak debugging**: +```bash +# Check Flatpak remotes +flatpak remotes + +# Check installed Flatpaks +flatpak list + +# Install Flatpak manually +flatpak install -y flathub org.mozilla.firefox +``` + +**Homebrew debugging**: +```bash +# Check Homebrew status +brew doctor + +# Check Brewfile +cat /usr/share/ublue-os/homebrew/default.Brewfile + +# Install manually +brew install package-name +``` + +--- + +## Resources & Documentation + +- **Bluefin patterns**: https://github.com/ublue-os/bluefin +- **bootc documentation**: https://github.com/containers/bootc +- **Conventional Commits**: https://www.conventionalcommits.org/ +- **RPMfusion packages**: https://mirrors.rpmfusion.org/ +- **Flatpak IDs**: https://flathub.org/ +- **Homebrew**: https://brew.sh/ +- **Universal Blue**: https://universal-blue.org/ +- **Renovate**: https://docs.renovatebot.com/ +- **GitHub Actions**: https://docs.github.com/en/actions +- **Podman**: https://podman.io/ +- **Justfile**: https://just.systems/ + +--- + +## Other Rules that are Important to the Maintainers + +- Ensure that [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) are used and enforced for every commit and pull request title. +- Always be surgical with the least amount of code, the project strives to be easy to maintain. + +## Attribution Requirements + +AI agents must disclose what tool and model they are using in the "Assisted-by" commit footer: + +```text +Assisted-by: [Model Name] via [Tool Name] +``` + +Example: + +```text +Assisted-by: Claude 3.5 Sonnet via GitHub Copilot +``` + +--- + +**Last Updated**: 2025-11-14 +**Template Version**: finpilot (Enhanced with comprehensive Copilot instructions) +**Maintainer**: Universal Blue Community diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e434fdb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# CONTRIBUTING + +Thanks for helping out! + +Check the [Contributing Guide](https://docs.projectbluefin.io/contributing) for contribution information. + +This repository is for building the images, you are probably looking for [@projectbluefin/common](https://github.com/projectbluefin/common) to change something in Bluefin. Make sure you check [the architecture diagram](https://docs.projectbluefin.io/contributing#understanding-bluefins-architecture). diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..828af47 --- /dev/null +++ b/Containerfile @@ -0,0 +1,87 @@ +############################################################################### +# PROJECT NAME CONFIGURATION +############################################################################### +# Name: finpilot +# +# IMPORTANT: Change "finpilot" above to your desired project name. +# This name should be used consistently throughout the repository in: +# - Justfile: export image_name := env("IMAGE_NAME", "your-name-here") +# - README.md: # your-name-here (title) +# - artifacthub-repo.yml: repositoryID: your-name-here +# - custom/ujust/README.md: localhost/your-name-here:stable (in bootc switch example) +# +# The project name defined here is the single source of truth for your +# custom image's identity. When changing it, update all references above +# to maintain consistency. +############################################################################### + +############################################################################### +# MULTI-STAGE BUILD ARCHITECTURE +############################################################################### +# This Containerfile follows the Bluefin architecture pattern as implemented in +# @projectbluefin/distroless. The architecture layers OCI containers together: +# +# 1. Context Stage (ctx) - Combines resources from: +# - Local build scripts and custom files +# - @projectbluefin/common - Desktop configuration shared with Aurora +# - @ublue-os/brew - Homebrew integration +# +# 2. Base Image Options: +# - `ghcr.io/ublue-os/silverblue-main:latest` (Fedora and GNOME) +# - `ghcr.io/ublue-os/base-main:latest` (Fedora and no desktop +# - `quay.io/centos-bootc/centos-bootc:stream10 (CentOS-based)` +# +# See: https://docs.projectbluefin.io/contributing/ for architecture diagram +############################################################################### + +# Context stage - combine local and imported OCI container resources +FROM scratch AS ctx + +COPY build /build +COPY custom /custom +# Copy from OCI containers to distinct subdirectories to avoid conflicts +# Note: Renovate can automatically update these :latest tags to SHA-256 digests for reproducibility +COPY --from=ghcr.io/projectbluefin/common:latest /system_files /oci/common +COPY --from=ghcr.io/ublue-os/brew:latest /system_files /oci/brew + +# Base Image - GNOME included +FROM ghcr.io/ublue-os/silverblue-main:latest + +## Alternative base images, no desktop included (uncomment to use): +# FROM ghcr.io/ublue-os/base-main:latest +# FROM quay.io/centos-bootc/centos-bootc:stream10 + +## Alternative GNOME OS base image (uncomment to use): +# FROM quay.io/gnome_infrastructure/gnome-build-meta:gnomeos-nightly + +### /opt +## Some bootable images, like Fedora, have /opt symlinked to /var/opt, in order to +## make it mutable/writable for users. However, some packages write files to this directory, +## thus its contents might be wiped out when bootc deploys an image, making it troublesome for +## some packages. Eg, google-chrome, docker-desktop. +## +## Uncomment the following line if one desires to make /opt immutable and be able to be used +## by the package manager. + +# RUN rm /opt && mkdir /opt + +### MODIFICATIONS +## Make modifications desired in your image and install packages by modifying the build scripts. +## The following RUN directive mounts the ctx stage which includes: +## - Local build scripts from /build +## - Local custom files from /custom +## - Files from @projectbluefin/common at /oci/common +## - Files from @projectbluefin/branding at /oci/branding +## - Files from @ublue-os/artwork at /oci/artwork +## - Files from @ublue-os/brew at /oci/brew +## Scripts are run in numerical order (10-build.sh, 20-example.sh, etc.) + +RUN --mount=type=bind,from=ctx,source=/,target=/ctx \ + --mount=type=cache,dst=/var/cache \ + --mount=type=cache,dst=/var/log \ + --mount=type=tmpfs,dst=/tmp \ + /ctx/build/10-build.sh + +### LINTING +## Verify final image and contents are correct. +RUN bootc container lint diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..5accbe1 --- /dev/null +++ b/Justfile @@ -0,0 +1,318 @@ +export image_name := env("IMAGE_NAME", "finpilot") +export default_tag := env("DEFAULT_TAG", "stable") +export bib_image := env("BIB_IMAGE", "quay.io/centos-bootc/bootc-image-builder:latest@sha256:903c01d110b8533f8891f07c69c0ba2377f8d4bc7e963311082b7028c04d529d") + +alias build-vm := build-qcow2 +alias rebuild-vm := rebuild-qcow2 +alias run-vm := run-vm-qcow2 + +[private] +default: + @just --list + +# Check Just Syntax +[group('Just')] +check: + #!/usr/bin/bash + find . -type f -name "*.just" | while read -r file; do + echo "Checking syntax: $file" + just --unstable --fmt --check -f $file + done + echo "Checking syntax: Justfile" + just --unstable --fmt --check -f Justfile + +# Fix Just Syntax +[group('Just')] +fix: + #!/usr/bin/bash + find . -type f -name "*.just" | while read -r file; do + echo "Checking syntax: $file" + just --unstable --fmt -f $file + done + echo "Checking syntax: Justfile" + just --unstable --fmt -f Justfile || { exit 1; } + +# Clean Repo +[group('Utility')] +clean: + #!/usr/bin/bash + set -eoux pipefail + touch _build + find *_build* -exec rm -rf {} \; + rm -f previous.manifest.json + rm -f changelog.md + rm -f output.env + rm -f output/ + +# Sudo Clean Repo +[group('Utility')] +[private] +sudo-clean: + just sudoif just clean + +# sudoif bash function +[group('Utility')] +[private] +sudoif command *args: + #!/usr/bin/bash + function sudoif(){ + if [[ "${UID}" -eq 0 ]]; then + "$@" + elif [[ "$(command -v sudo)" && -n "${SSH_ASKPASS:-}" ]] && [[ -n "${DISPLAY:-}" || -n "${WAYLAND_DISPLAY:-}" ]]; then + /usr/bin/sudo --askpass "$@" || exit 1 + elif [[ "$(command -v sudo)" ]]; then + /usr/bin/sudo "$@" || exit 1 + else + exit 1 + fi + } + sudoif {{ command }} {{ args }} + +# This Justfile recipe builds a container image using Podman. +# +# Arguments: +# $target_image - The tag you want to apply to the image (default: $image_name). +# $tag - The tag for the image (default: $default_tag). +# +# The script constructs the version string using the tag and the current date. +# If the git working directory is clean, it also includes the short SHA of the current HEAD. +# +# just build $target_image $tag +# +# Example usage: +# just build aurora lts +# +# This will build an image 'aurora:lts' with DX and GDX enabled. +# + +# Build the image using the specified parameters +build $target_image=image_name $tag=default_tag: + #!/usr/bin/env bash + + BUILD_ARGS=() + if [[ -z "$(git status -s)" ]]; then + BUILD_ARGS+=("--build-arg" "SHA_HEAD_SHORT=$(git rev-parse --short HEAD)") + fi + + podman build \ + "${BUILD_ARGS[@]}" \ + --pull=newer \ + --tag "${target_image}:${tag}" \ + . + +# Command: _rootful_load_image +# Description: This script checks if the current user is root or running under sudo. If not, it attempts to resolve the image tag using podman inspect. +# If the image is found, it loads it into rootful podman. If the image is not found, it pulls it from the repository. +# +# Parameters: +# $target_image - The name of the target image to be loaded or pulled. +# $tag - The tag of the target image to be loaded or pulled. Default is 'default_tag'. +# +# Example usage: +# _rootful_load_image my_image latest +# +# Steps: +# 1. Check if the script is already running as root or under sudo. +# 2. Check if target image is in the non-root podman container storage) +# 3. If the image is found, load it into rootful podman using podman scp. +# 4. If the image is not found, pull it from the remote repository into reootful podman. + +_rootful_load_image $target_image=image_name $tag=default_tag: + #!/usr/bin/bash + set -eoux pipefail + + # Check if already running as root or under sudo + 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 + + # Try to resolve the image tag using podman inspect + set +e + resolved_tag=$(podman inspect -t image "${target_image}:${tag}" | jq -r '.[].RepoTags.[0]') + return_code=$? + set -e + + USER_IMG_ID=$(podman images --filter reference="${target_image}:${tag}" --format "'{{ '{{.ID}}' }}'") + + if [[ $return_code -eq 0 ]]; then + # If the image is found, load it into rootful podman + ID=$(just sudoif podman images --filter reference="${target_image}:${tag}" --format "'{{ '{{.ID}}' }}'") + if [[ "$ID" != "$USER_IMG_ID" ]]; then + # If the image ID is not found or different from user, copy the image from user podman to root podman + COPYTMP=$(mktemp -p "${PWD}" -d -t _build_podman_scp.XXXXXXXXXX) + just sudoif TMPDIR=${COPYTMP} podman image scp ${UID}@localhost::"${target_image}:${tag}" root@localhost::"${target_image}:${tag}" + rm -rf "${COPYTMP}" + fi + else + # If the image is not found, pull it from the repository + just sudoif podman pull "${target_image}:${tag}" + fi + +# Build a bootc bootable image using Bootc Image Builder (BIB) +# Converts a container image to a bootable image +# Parameters: +# target_image: The name of the image to build (ex. localhost/fedora) +# tag: The tag of the image to build (ex. latest) +# type: The type of image to build (ex. qcow2, raw, iso) +# config: The configuration file to use for the build (default: iso/disk.toml) + +# Example: just _rebuild-bib localhost/fedora latest qcow2 iso/disk.toml +_build-bib $target_image $tag $type $config: (_rootful_load_image target_image tag) + #!/usr/bin/env bash + set -euo pipefail + + args="--type ${type} " + args+="--use-librepo=True " + args+="--rootfs=btrfs" + + BUILDTMP=$(mktemp -p "${PWD}" -d -t _build-bib.XXXXXXXXXX) + + sudo podman run \ + --rm \ + -it \ + --privileged \ + --pull=newer \ + --net=host \ + --security-opt label=type:unconfined_t \ + -v $(pwd)/${config}:/config.toml:ro \ + -v $BUILDTMP:/output \ + -v /var/lib/containers/storage:/var/lib/containers/storage \ + "${bib_image}" \ + ${args} \ + "${target_image}:${tag}" + + mkdir -p output + sudo mv -f $BUILDTMP/* output/ + sudo rmdir $BUILDTMP + sudo chown -R $USER:$USER output/ + +# Podman builds the image from the Containerfile and creates a bootable image +# Parameters: +# target_image: The name of the image to build (ex. localhost/fedora) +# tag: The tag of the image to build (ex. latest) +# type: The type of image to build (ex. qcow2, raw, iso) +# config: The configuration file to use for the build (deafult: iso/disk.toml) + +# Example: just _rebuild-bib localhost/fedora latest qcow2 iso/disk.toml +_rebuild-bib $target_image $tag $type $config: (build target_image tag) && (_build-bib target_image tag type config) + +# Build a QCOW2 virtual machine image +[group('Build Virtal Machine Image')] +build-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "qcow2" "iso/disk.toml") + +# Build a RAW virtual machine image +[group('Build Virtal Machine Image')] +build-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "raw" "iso/disk.toml") + +# Build an ISO virtual machine image +[group('Build Virtal Machine Image')] +build-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "iso" "iso/iso.toml") + +# Rebuild a QCOW2 virtual machine image +[group('Build Virtal Machine Image')] +rebuild-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "qcow2" "iso/disk.toml") + +# Rebuild a RAW virtual machine image +[group('Build Virtal Machine Image')] +rebuild-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "raw" "iso/disk.toml") + +# Rebuild an ISO virtual machine image +[group('Build Virtal Machine Image')] +rebuild-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "iso" "iso/iso.toml") + +# Run a virtual machine with the specified image type and configuration +_run-vm $target_image $tag $type $config: + #!/usr/bin/bash + set -eoux pipefail + + # Determine the image file based on the type + image_file="output/${type}/disk.${type}" + if [[ $type == iso ]]; then + image_file="output/bootiso/install.iso" + fi + + # Build the image if it does not exist + if [[ ! -f "${image_file}" ]]; then + just "build-${type}" "$target_image" "$tag" + fi + + # Determine an available port to use + port=8006 + while grep -q :${port} <<< $(ss -tunalp); do + port=$(( port + 1 )) + done + echo "Using Port: ${port}" + echo "Connect to http://localhost:${port}" + + # Set up the arguments for running the VM + 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 "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) + + # Run the VM and open the browser to connect + (sleep 30 && xdg-open http://localhost:"$port") & + podman run "${run_args[@]}" + +# Run a virtual machine from a QCOW2 image +[group('Run Virtal Machine')] +run-vm-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "qcow2" "iso/disk.toml") + +# Run a virtual machine from a RAW image +[group('Run Virtal Machine')] +run-vm-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "raw" "iso/disk.toml") + +# Run a virtual machine from an ISO +[group('Run Virtal Machine')] +run-vm-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "iso" "iso/iso.toml") + +# Run a virtual machine using systemd-vmspawn +[group('Run Virtal Machine')] +spawn-vm rebuild="0" type="qcow2" ram="6G": + #!/usr/bin/env bash + + set -euo pipefail + + [ "{{ rebuild }}" -eq 1 ] && echo "Rebuilding the ISO" && just build-vm {{ rebuild }} {{ 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 }} + +# Runs shell check on all Bash scripts +lint: + #!/usr/bin/env bash + set -eoux pipefail + # Check if shellcheck is installed + if ! command -v shellcheck &> /dev/null; then + echo "shellcheck could not be found. Please install it." + exit 1 + fi + # Run shellcheck on all Bash scripts + /usr/bin/find . -iname "*.sh" -type f -exec shellcheck "{}" ';' + +# Runs shfmt on all Bash scripts +format: + #!/usr/bin/env bash + set -eoux pipefail + # Check if shfmt is installed + if ! command -v shfmt &> /dev/null; then + echo "shellcheck could not be found. Please install it." + exit 1 + fi + # Run shfmt on all Bash scripts + /usr/bin/find . -iname "*.sh" -type f -exec shfmt --write "{}" ';' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae1278a --- /dev/null +++ b/README.md @@ -0,0 +1,349 @@ +# finpilot + +A template for building custom bootc operating system images based on the lessons from [Universal Blue](https://universal-blue.org/) and [Bluefin](https://projectbluefin.io). It is designed to be used manually, but is optimized to be bootstraped by GitHub Copilot. After set up you'll have your own custom Linux. + +This template uses the **multi-stage build architecture** from , combining resources from multiple OCI containers for modularity and maintainability. See the [Architecture](#architecture) section below for details. + +**Unlike previous templates, you are not modifying Bluefin and making changes.**: You are assembling your own Bluefin in the same exact way that Bluefin, Aurora, and Bluefin LTS are built. This is way more flexible and better for everyone since the image-agnostic and desktop things we love about Bluefin lives in @projectbluefin/common. + + Instead, you create your own OS repository based on this template, allowing full customization while leveraging Bluefin's robust build system and shared components. + +> Be the one who moves, not the one who is moved. + +## Guided Copilot Mode + +Here are the steps to guide copilot to make your own repo, or just use it like a regular image template. + +1. Click the green "Use this as a template" button and create a new repository +2. Select your owner, pick a repo name for your OS, and a description +3. In the "Jumpstart your project with Copilot (optional)" add this, modify to your liking: + +``` +Use @projectbluefin/finpilot as a template, name the OS the repository name. Ensure the entire operating system is bootstrapped. Ensure all github actions are enabled and running. Ensure the README has the github setup instructions for cosign and the other steps required to finish the task. +``` + +## What's Included + +### Build System +- Automated builds via GitHub Actions on every commit +- Awesome self hosted Renovate setup that keeps all your images and actions up to date. +- Automatic cleanup of old images (90+ days) to keep it tidy +- Pull request workflow - test changes before merging to main + - PRs build and validate before merge + - `main` branch builds `:stable` images +- Validates your files on pull requests so you never break a build: + - Brewfile, Justfile, ShellCheck, Renovate config, and it'll even check to make sure the flatpak you add exists on FlatHub +- Production Grade Features + - Container signing and SBOM Generation + - See checklist below to enable these as they take some manual configuration + +### Homebrew Integration +- Pre-configured Brewfiles for easy package installation and customization +- Includes curated collections: development tools, fonts, CLI utilities. Go nuts. +- Users install packages at runtime with `brew bundle`, aliased to premade `ujust commands` +- See [custom/brew/README.md](custom/brew/README.md) for details + +### Flatpak Support +- Ship your favorite flatpaks +- Automatically installed on first boot after user setup +- See [custom/flatpaks/README.md](custom/flatpaks/README.md) for details + +### ujust Commands +- User-friendly command shortcuts via `ujust` +- Pre-configured examples for app installation and system maintenance for you to customize +- See [custom/ujust/README.md](custom/ujust/README.md) for details + +### Build Scripts +- Modular numbered scripts (10-, 20-, 30-) run in order +- Example scripts included for third-party repositories and desktop replacement +- Helper functions for safe COPR usage +- See [build/README.md](build/README.md) for details + +## Quick Start + +### 1. Create Your Repository + +Click "Use this template" to create a new repository from this template. + +### 2. Rename the Project + +Important: Change `finpilot` to your repository name in these 6 files: + +1. `Containerfile` (line 4): `# Name: your-repo-name` +2. `Justfile` (line 1): `export image_name := env("IMAGE_NAME", "your-repo-name")` +3. `README.md` (line 1): `# your-repo-name` +4. `artifacthub-repo.yml` (line 5): `repositoryID: your-repo-name` +5. `custom/ujust/README.md` (~line 175): `localhost/your-repo-name:stable` +6. `.github/workflows/clean.yml` (line 23): `packages: your-repo-name` + +### 3. Enable GitHub Actions + +- Go to the "Actions" tab in your repository +- Click "I understand my workflows, go ahead and enable them" + +Your first build will start automatically! + +Note: Image signing is disabled by default. Your images will build successfully without any signing keys. Once you're ready for production, see "Optional: Enable Image Signing" below. + +### 4. Customize Your Image + +Choose your base image in `Containerfile` (line 23): +```dockerfile +FROM ghcr.io/ublue-os/bluefin:stable +``` + +Add your packages in `build/10-build.sh`: +```bash +dnf5 install -y package-name +``` + +Customize your apps: +- Add Brewfiles in `custom/brew/` ([guide](custom/brew/README.md)) +- Add Flatpaks in `custom/flatpaks/` ([guide](custom/flatpaks/README.md)) +- Add ujust commands in `custom/ujust/` ([guide](custom/ujust/README.md)) + +### 5. Development Workflow + +All changes should be made via pull requests: + +1. Open a pull request on GitHub with the change you want. +3. The PR will automatically trigger: + - Build validation + - Brewfile, Flatpak, Justfile, and shellcheck validation + - Test image build +4. Once checks pass, merge the PR +5. Merging triggers publishes a `:stable` image + +### 6. Deploy Your Image + +Switch to your image: +```bash +sudo bootc switch ghcr.io/your-username/your-repo-name:stable +sudo systemctl reboot +``` + +## Optional: Enable Image Signing + +Image signing is disabled by default to let you start building immediately. However, signing is strongly recommended for production use. + +### Why Sign Images? + +- Verify image authenticity and integrity +- Prevent tampering and supply chain attacks +- Required for some enterprise/security-focused deployments +- Industry best practice for production images + +### Setup Instructions + +1. Generate signing keys: +```bash +cosign generate-key-pair +``` + +This creates two files: +- `cosign.key` (private key) - Keep this secret +- `cosign.pub` (public key) - Commit this to your repository + +2. Add the private key to GitHub Secrets: + - Copy the entire contents of `cosign.key` + - Go to your repository on GitHub + - Navigate to Settings → Secrets and variables → Actions ([GitHub docs](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)) + - Click "New repository secret" + - Name: `SIGNING_SECRET` + - Value: Paste the entire contents of `cosign.key` + - Click "Add secret" + +3. Replace the contents of `cosign.pub` with your public key: + - Open `cosign.pub` in your repository + - Replace the placeholder with your actual public key + - Commit and push the change + +4. Enable signing in the workflow: + - Edit `.github/workflows/build.yml` + - Find the "OPTIONAL: Image Signing with Cosign" section. + - Uncomment the steps to install Cosign and sign the image (remove the `#` from the beginning of each line in that section). + - Commit and push the change + +5. Your next build will produce signed images! + +Important: Never commit `cosign.key` to the repository. It's already in `.gitignore`. + +## Love Your Image? Let's Go to Production + +Ready to take your custom OS to production? Enable these features for enhanced security, reliability, and performance: + +### Production Checklist + +- [ ] **Enable Image Signing** (Recommended) + - Provides cryptographic verification of your images + - Prevents tampering and ensures authenticity + - See "Optional: Enable Image Signing" section above for setup instructions + - Status: **Disabled by default** to allow immediate testing + +- [ ] **Enable SBOM Attestation** (Recommended) + - Generates Software Bill of Materials for supply chain security + - Provides transparency about what's in your image + - Requires image signing to be enabled first + - To enable: + 1. First complete image signing setup above + 2. Edit `.github/workflows/build.yml` + 3. Find the "OPTIONAL: SBOM Attestation" section around line 232 + 4. Uncomment the "Add SBOM Attestation" step + 5. Commit and push + - Status: **Disabled by default** (requires signing first) + +- [ ] **Enable Image Rechunking** (Recommended) + - Optimizes bootc image layers for better update performance + - Reduces update sizes by 5-10x + - Improves download resumability with evenly sized layers + - To enable: + 1. Edit `.github/workflows/build.yml` + 2. Find the "Build Image" step + 3. Add a rechunk step after the build (see example below) + - Status: **Not enabled by default** (optional optimization) + +#### Adding Image Rechunking + +After building your bootc image, add a rechunk step before pushing to the registry. Here's an example based on the workflow used by [zirconium-dev/zirconium](https://github.com/zirconium-dev/zirconium): + +```yaml +- name: Build image + id: build + run: sudo podman build -t "${IMAGE_NAME}:${DEFAULT_TAG}" -f ./Containerfile . + +- name: Rechunk Image + run: | + sudo podman run --rm --privileged \ + -v /var/lib/containers:/var/lib/containers \ + --entrypoint /usr/libexec/bootc-base-imagectl \ + "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" \ + rechunk --max-layers 96 \ + "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" \ + "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" + +- name: Push to Registry + run: sudo podman push "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" "${IMAGE_REGISTRY}/${IMAGE_NAME}:${DEFAULT_TAG}" +``` + +Alternative approach using a temporary tag for clarity: + +```yaml +- name: Rechunk Image + run: | + sudo podman run --rm --privileged \ + -v /var/lib/containers:/var/lib/containers \ + --entrypoint /usr/libexec/bootc-base-imagectl \ + "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" \ + rechunk --max-layers 67 \ + "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" \ + "localhost/${IMAGE_NAME}:${DEFAULT_TAG}-rechunked" + + # Tag the rechunked image with the original tag + sudo podman tag "localhost/${IMAGE_NAME}:${DEFAULT_TAG}-rechunked" "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" + sudo podman rmi "localhost/${IMAGE_NAME}:${DEFAULT_TAG}-rechunked" +``` + +**Parameters:** +- `--max-layers`: Maximum number of layers for the rechunked image (typically 67 for optimal balance) +- The first image reference is the source (input) +- The second image reference is the destination (output) + - When using the same reference for both, the image is rechunked in-place + - You can also use different tags (e.g., `-rechunked` suffix) and then retag if preferred + +**References:** +- [CoreOS rpm-ostree build-chunked-oci documentation](https://coreos.github.io/rpm-ostree/build-chunked-oci/) +- [bootc documentation](https://containers.github.io/bootc/) + +### After Enabling Production Features + +Your workflow will: +- Sign all images with your key +- Generate and attach SBOMs +- Provide full supply chain transparency + +Users can verify your images with: +```bash +cosign verify --key cosign.pub ghcr.io/your-username/your-repo-name:stable +``` + +## Detailed Guides + +- [Homebrew/Brewfiles](custom/brew/README.md) - Runtime package management +- [Flatpak Preinstall](custom/flatpaks/README.md) - GUI application setup +- [ujust Commands](custom/ujust/README.md) - User convenience commands +- [Build Scripts](build/README.md) - Build-time customization + +## Architecture + +This template follows the **multi-stage build architecture** from @projectbluefin/distroless, as documented in the [Bluefin Contributing Guide](https://docs.projectbluefin.io/contributing/). + +### Multi-Stage Build Pattern + +**Stage 1: Context (ctx)** - Combines resources from multiple sources: +- Local build scripts (`/build`) +- Local custom files (`/custom`) +- **@projectbluefin/common** - Desktop configuration shared with Aurora +- **@projectbluefin/branding** - Branding assets +- **@ublue-os/artwork** - Artwork shared with Aurora and Bazzite +- **@ublue-os/brew** - Homebrew integration + +**Stage 2: Base Image** - Default options: +- `ghcr.io/ublue-os/silverblue-main:latest` (Fedora-based, default) +- `quay.io/centos-bootc/centos-bootc:stream10` (CentOS-based alternative) + +### Benefits of This Architecture + +- **Modularity**: Compose your image from reusable OCI containers +- **Maintainability**: Update shared components independently +- **Reproducibility**: Renovate automatically updates OCI tags to SHA digests +- **Consistency**: Share components across Bluefin, Aurora, and custom images + +### OCI Container Resources + +The template imports files from these OCI containers at build time: + +```dockerfile +COPY --from=ghcr.io/ublue-os/base-main:latest /system_files /oci/base +COPY --from=ghcr.io/projectbluefin/common:latest /system_files /oci/common +COPY --from=ghcr.io/ublue-os/brew:latest /system_files /oci/brew +``` + +Your build scripts can access these files at: +- `/ctx/oci/base/` - Base system configuration +- `/ctx/oci/common/` - Shared desktop configuration +- `/ctx/oci/branding/` - Branding assets +- `/ctx/oci/artwork/` - Artwork files +- `/ctx/oci/brew/` - Homebrew integration files + +**Note**: Renovate automatically updates `:latest` tags to SHA digests for reproducible builds. + +## Local Testing + +Test your changes before pushing: + +```bash +just build # Build container image +just build-qcow2 # Build VM disk image +just run-vm-qcow2 # Test in browser-based VM +``` + +## Community + +- [Universal Blue Discord](https://discord.gg/WEu6BdFEtp) +- [bootc Discussion](https://github.com/bootc-dev/bootc/discussions) + +## Learn More + +- [Universal Blue Documentation](https://universal-blue.org/) +- [bootc Documentation](https://containers.github.io/bootc/) +- [Video Tutorial by TesterTech](https://www.youtube.com/watch?v=IxBl11Zmq5wE) + +## Security + +This template provides security features for production use: +- Optional SBOM generation (Software Bill of Materials) for supply chain transparency +- Optional image signing with cosign for cryptographic verification +- Automated security updates via Renovate +- Build provenance tracking + +These security features are disabled by default to allow immediate testing. When you're ready for production, see the "Love Your Image? Let's Go to Production" section above to enable them. diff --git a/artifacthub-repo.yml b/artifacthub-repo.yml new file mode 100644 index 0000000..135b579 --- /dev/null +++ b/artifacthub-repo.yml @@ -0,0 +1,8 @@ +# This file is completely optional, but if you want to index your image on https://artifacthub.io/ you can +# Sign up and add the Repository ID to the right field. Owners fields are optional. +# Examples: https://artifacthub.io/packages/search?ts_query_web=ublue&sort=relevance&page=1 + +repositoryID: finpilot # Fill in with your own credentials +owners: # (optional, used to claim repository ownership) + - name: Jane Containerface + email: jane@blueuniversal.com diff --git a/build/10-build.sh b/build/10-build.sh new file mode 100755 index 0000000..2179fd3 --- /dev/null +++ b/build/10-build.sh @@ -0,0 +1,65 @@ +#!/usr/bin/bash + +set -eoux pipefail + +############################################################################### +# Main Build Script +############################################################################### +# This script follows the @ublue-os/bluefin pattern for build scripts. +# It uses set -eoux pipefail for strict error handling and debugging. +############################################################################### + +# Source helper functions +# shellcheck source=/dev/null +source /ctx/build/copr-helpers.sh + +# Enable nullglob for all glob operations to prevent failures on empty matches +shopt -s nullglob + +echo "::group:: Copy Bluefin Config from Common" + +# Copy just files from @projectbluefin/common (includes 00-entry.just which imports 60-custom.just) +mkdir -p /usr/share/ublue-os/just/ +shopt -s nullglob +cp -r /ctx/oci/common/bluefin/usr/share/ublue-os/just/* /usr/share/ublue-os/just/ +shopt -u nullglob + +echo "::endgroup::" + +echo "::group:: Copy Custom Files" + +# Copy Brewfiles to standard location +mkdir -p /usr/share/ublue-os/homebrew/ +cp /ctx/custom/brew/*.Brewfile /usr/share/ublue-os/homebrew/ + +# Consolidate Just Files +find /ctx/custom/ujust -iname '*.just' -exec printf "\n\n" \; -exec cat {} \; >> /usr/share/ublue-os/just/60-custom.just + +# Copy Flatpak preinstall files +mkdir -p /etc/flatpak/preinstall.d/ +cp /ctx/custom/flatpaks/*.preinstall /etc/flatpak/preinstall.d/ + +echo "::endgroup::" + +echo "::group:: Install Packages" + +# Install packages using dnf5 +# Example: dnf5 install -y tmux + +# Example using COPR with isolated pattern: +# copr_install_isolated "ublue-os/staging" package-name + +echo "::endgroup::" + +echo "::group:: System Configuration" + +# Enable/disable systemd services +systemctl enable podman.socket +# Example: systemctl mask unwanted-service + +echo "::endgroup::" + +# Restore default glob behavior +shopt -u nullglob + +echo "Custom build complete!" diff --git a/build/20-onepassword.sh.example b/build/20-onepassword.sh.example new file mode 100755 index 0000000..9c20647 --- /dev/null +++ b/build/20-onepassword.sh.example @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +# Tell build process to exit if there are any errors. +set -oue pipefail + +############################################################################### +# Example: Installing 1Password and Google Chrome from Official Repositories +############################################################################### +# This is an EXAMPLE file showing how to add third-party RPM repositories +# and install packages from them following Universal Blue/Bluefin conventions. +# +# To use this script: +# 1. Rename this file to remove the .example extension: 20-onepassword.sh +# 2. The build system will automatically run scripts in numerical order +# +# IMPORTANT CONVENTIONS (from @ublue-os/bluefin): +# - Always clean up temporary repository files after installation +# - Use dnf5 exclusively (never dnf or yum) +# - Always use -y flag for non-interactive operations +# - Remove repo files to keep the image clean (repos don't work at runtime) +############################################################################### + +### Install Google Chrome from Official Repository +echo "Installing Google Chrome..." + +# Add Google Chrome RPM repository +cat > /etc/yum.repos.d/google-chrome.repo << 'EOF' +[google-chrome] +name=google-chrome +baseurl=https://dl.google.com/linux/chrome/rpm/stable/x86_64 +enabled=1 +gpgcheck=1 +gpgkey=https://dl.google.com/linux/linux_signing_key.pub +EOF + +# Install Chrome +dnf5 install -y google-chrome-stable + +# Clean up repo file (required - repos don't work at runtime in bootc images) +rm -f /etc/yum.repos.d/google-chrome.repo + +echo "Google Chrome installed successfully" + +### Install 1Password from Official Repository +echo "Installing 1Password..." + +# Add 1Password RPM repository GPG key +rpm --import https://downloads.1password.com/linux/keys/1password.asc + +# Add 1Password RPM repository +cat > /etc/yum.repos.d/1password.repo << 'EOF' +[1password] +name=1Password Stable Channel +baseurl=https://downloads.1password.com/linux/rpm/stable/$basearch +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://downloads.1password.com/linux/keys/1password.asc +EOF + +# Install 1Password +dnf5 install -y 1password + +# Clean up repo file (required - repos don't work at runtime in bootc images) +rm -f /etc/yum.repos.d/1password.repo + +echo "1Password installed successfully" +echo "Chrome and 1Password installation complete!" diff --git a/build/30-cosmic-desktop.sh.example b/build/30-cosmic-desktop.sh.example new file mode 100755 index 0000000..2e4a7dc --- /dev/null +++ b/build/30-cosmic-desktop.sh.example @@ -0,0 +1,92 @@ +#!/usr/bin/bash + +set -eoux pipefail + +############################################################################### +# Example: Swap GNOME Desktop with COSMIC Desktop +############################################################################### +# This example demonstrates replacing the GNOME desktop environment with +# System76's COSMIC desktop from their COPR repository. +# +# COSMIC is a new desktop environment built in Rust by System76. +# https://github.com/pop-os/cosmic-epoch +# +# To use this script: +# 1. Rename to remove .example extension: mv 30-cosmic-desktop.sh.example 30-cosmic-desktop.sh +# 2. Build - scripts run in numerical order automatically +# +# WARNING: This removes GNOME and replaces it with COSMIC. Only use this if +# you want COSMIC as your desktop environment instead of GNOME. +############################################################################### + +# Source helper functions +# shellcheck source=/dev/null +source /ctx/build/copr-helpers.sh + +echo "::group:: Remove GNOME Desktop" + +# Remove GNOME Shell and related packages +dnf5 remove -y \ + gnome-shell \ + gnome-shell-extension* \ + gnome-terminal \ + gnome-software \ + gnome-control-center \ + nautilus \ + gdm + +echo "GNOME desktop removed" +echo "::endgroup::" + +echo "::group:: Install COSMIC Desktop" + +# Install COSMIC desktop from System76's COPR +# Using isolated pattern to prevent COPR from persisting +copr_install_isolated "ryanabx/cosmic-epoch" \ + cosmic-session \ + cosmic-greeter \ + cosmic-comp \ + cosmic-panel \ + cosmic-launcher \ + cosmic-applets \ + cosmic-settings \ + cosmic-files \ + cosmic-edit \ + cosmic-term \ + cosmic-workspaces + +echo "COSMIC desktop installed successfully" +echo "::endgroup::" + +echo "::group:: Configure Display Manager" + +# Enable cosmic-greeter (COSMIC's display manager) +systemctl enable cosmic-greeter + +# Set COSMIC as default session +mkdir -p /etc/X11/sessions +cat > /etc/X11/sessions/cosmic.desktop << 'COSMICDESKTOP' +[Desktop Entry] +Name=COSMIC +Comment=COSMIC Desktop Environment +Exec=cosmic-session +Type=Application +DesktopNames=COSMIC +COSMICDESKTOP + +echo "Display manager configured" +echo "::endgroup::" + +echo "::group:: Install Additional Utilities" + +# Install additional utilities that work well with COSMIC +dnf5 install -y \ + kitty \ + flatpak \ + xdg-desktop-portal-cosmic + +echo "Additional utilities installed" +echo "::endgroup::" + +echo "COSMIC desktop installation complete!" +echo "After booting, select 'COSMIC' session at the login screen" diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..c0cb90e --- /dev/null +++ b/build/README.md @@ -0,0 +1,77 @@ +# Build Scripts + +This directory contains build scripts that run during image creation. Scripts are executed in numerical order. + +## How It Works + +Scripts are named with a number prefix (e.g., `10-build.sh`, `20-onepassword.sh`) and run in ascending order during the container build process. + +## Included Scripts + +- **`10-build.sh`** - Main build script for base system modifications, package installation, and service configuration + +## Example Scripts + +- **`20-onepassword.sh.example`** - Example showing how to install software from third-party RPM repositories (Google Chrome, 1Password) + +To use an example script: +1. Remove the `.example` extension +2. Make it executable: `chmod +x build/20-yourscript.sh` +3. The build system will automatically run it in numerical order + +## Creating Your Own Scripts + +Create numbered scripts for different purposes: + +```bash +# 10-build.sh - Base system (already exists) +# 20-drivers.sh - Hardware drivers +# 30-development.sh - Development tools +# 40-gaming.sh - Gaming software +# 50-cleanup.sh - Final cleanup tasks +``` + +### Script Template + +```bash +#!/usr/bin/env bash +set -oue pipefail + +echo "Running custom setup..." +# Your commands here +``` + +### Best Practices + +- **Use descriptive names**: `20-nvidia-drivers.sh` is better than `20-stuff.sh` +- **One purpose per script**: Easier to debug and maintain +- **Clean up after yourself**: Remove temporary files and disable temporary repos +- **Test incrementally**: Add one script at a time and test builds +- **Comment your code**: Future you will thank present you + +### Disabling Scripts + +To temporarily disable a script without deleting it: +- Rename it with `.disabled` extension: `20-script.sh.disabled` +- Or remove execute permission: `chmod -x build/20-script.sh` + +## Execution Order + +The Containerfile runs scripts like this: + +```dockerfile +RUN /ctx/build/10-build.sh +``` + +If you want to run multiple scripts, you can: + +1. **Modify Containerfile** to run each script explicitly +2. **Create a runner script** that executes all numbered scripts +3. **Use the default** and keep everything in `10-build.sh` (simplest) + +## Notes + +- Scripts run as root during build +- Build context is available at `/ctx` +- Use dnf5 for package management (not dnf or yum) +- Always use `-y` flag for non-interactive installs diff --git a/build/copr-helpers.sh b/build/copr-helpers.sh new file mode 100755 index 0000000..e82d4be --- /dev/null +++ b/build/copr-helpers.sh @@ -0,0 +1,30 @@ +#!/usr/bin/bash +set -euo pipefail + +############################################################################### +# COPR Helper Functions +############################################################################### +# These helper functions follow the @ublue-os/bluefin pattern for managing +# COPR repositories in a safe, isolated manner. +############################################################################### + +copr_install_isolated() { + local copr_name="$1" + shift + local packages=("$@") + + if [[ ${#packages[@]} -eq 0 ]]; then + echo "ERROR: No packages specified for copr_install_isolated" + return 1 + fi + + repo_id="copr:copr.fedorainfracloud.org:${copr_name//\//:}" + + echo "Installing ${packages[*]} from COPR $copr_name (isolated)" + + dnf5 -y copr enable "$copr_name" + dnf5 -y copr disable "$copr_name" + dnf5 -y install --enablerepo="$repo_id" "${packages[@]}" + + echo "Installed ${packages[*]} from $copr_name" +} diff --git a/cosign.pub b/cosign.pub new file mode 100644 index 0000000..19e92f7 --- /dev/null +++ b/cosign.pub @@ -0,0 +1,16 @@ +-----BEGIN PUBLIC KEY----- +REPLACE THIS TEXT WITH YOUR ACTUAL PUBLIC KEY +Generated by running: cosign generate-key-pair +After generating, paste the contents of your cosign.pub file here +-----END PUBLIC KEY----- + +Instructions: +1. Run: cosign generate-key-pair +2. This creates cosign.key (private) and cosign.pub (public) +3. Copy the entire contents of cosign.pub +4. Replace this entire file with that content +5. Commit and push this file +6. Add cosign.key contents to GitHub Secrets as SIGNING_SECRET + (See README.md for detailed instructions) + +Never commit cosign.key - it must remain private! diff --git a/custom/brew/README.md b/custom/brew/README.md new file mode 100644 index 0000000..4559f13 --- /dev/null +++ b/custom/brew/README.md @@ -0,0 +1,74 @@ +# Homebrew Integration + +This directory contains Brewfile declarations that will be copied into your custom image at `/usr/share/ublue-os/homebrew/`. + +## What are Brewfiles? + +Brewfiles are Homebrew's way of declaring packages in a declarative format. They allow you to specify which packages, taps, and casks you want installed. + +## How It Works + +1. **During Build**: Files in this directory are copied to `/usr/share/ublue-os/homebrew/` in the image +2. **After Installation**: Users install packages by running `brew bundle` commands +3. **User Experience**: Declarative package management via Homebrew + +## Usage + +### Adding Brewfiles to Your Image + +1. Create `.Brewfile` files in this directory +2. Add your desired packages using Brewfile syntax +3. Build your image - the Brewfiles will be copied to `/usr/share/ublue-os/homebrew/` + +**Example Files in this directory:** +- [`default.Brewfile`](default.Brewfile) - Essential command-line tools +- [`development.Brewfile`](development.Brewfile) - Development tools and languages +- [`fonts.Brewfile`](fonts.Brewfile) - Programming fonts + +### Installing Packages from Brewfiles + +After booting into your custom image, install packages with: + +```bash +brew bundle --file /usr/share/ublue-os/homebrew/default.Brewfile +``` + +Or use the convenient ujust commands defined in [`custom/ujust/custom-apps.just`](../ujust/custom-apps.just): +```bash +ujust install-default-apps +ujust install-dev-tools +ujust install-fonts +``` + +## File Format + +Brewfiles use Ruby syntax: + +```ruby +# Add a tap (third-party repository) +tap "homebrew/cask" + +# Install a formula (CLI tool) +brew "bat" +brew "eza" +brew "ripgrep" + +# Install a cask (GUI application, macOS only) +cask "visual-studio-code" +``` + +## Customization + +Edit the existing Brewfiles or create new ones: +- **[`default.Brewfile`](default.Brewfile)** - Modify for your essential tools +- **[`development.Brewfile`](development.Brewfile)** - Add your dev stack +- **[`fonts.Brewfile`](fonts.Brewfile)** - Add preferred fonts +- **Create new files** - `gaming.Brewfile`, `media.Brewfile`, etc. + +When you add new Brewfiles, create corresponding ujust commands in [`custom/ujust/custom-apps.just`](../ujust/custom-apps.just) for easy installation. + +## Resources + +- [Homebrew Documentation](https://docs.brew.sh/) +- [Brewfile Documentation](https://github.com/Homebrew/homebrew-bundle) +- [Bluefin Homebrew Guide](https://docs.projectbluefin.io/administration#homebrew) diff --git a/custom/brew/default.Brewfile b/custom/brew/default.Brewfile new file mode 100644 index 0000000..5732042 --- /dev/null +++ b/custom/brew/default.Brewfile @@ -0,0 +1,22 @@ +# Default Brewfile for bluepilot +# Add your favorite brew packages here +# +# Examples: + +# Modern CLI tools +brew "bat" # cat with syntax highlighting +brew "eza" # Modern replacement for ls +brew "fd" # Simple, fast alternative to find +brew "rg" # ripgrep - faster grep + +# Development tools +brew "gh" # GitHub CLI +brew "git" # Git version control + +# Shell enhancements +brew "starship" # Cross-shell prompt +brew "zoxide" # Smarter cd command + +# Utilities +brew "htop" # Interactive process viewer +brew "tmux" # Terminal multiplexer diff --git a/custom/brew/development.Brewfile b/custom/brew/development.Brewfile new file mode 100644 index 0000000..2ad1555 --- /dev/null +++ b/custom/brew/development.Brewfile @@ -0,0 +1,26 @@ +# Brewfile for development tools +# Uncomment packages you want to install + +# Container and orchestration tools +# brew "kind" # Kubernetes in Docker +# brew "kubectl" # Kubernetes CLI +# brew "k9s" # Kubernetes TUI +# brew "helm" # Kubernetes package manager + +# Cloud tools +# brew "awscli" # AWS CLI +# brew "azure-cli" # Azure CLI + +# Programming languages and tools +# brew "go" # Go programming language +# brew "node" # Node.js +# brew "python@3.12" # Python +# brew "rust" # Rust programming language + +# Build tools +# brew "cmake" # Cross-platform build system +# brew "ninja" # Small build system + +# Debugging and profiling +# brew "gdb" # GNU debugger +# brew "valgrind" # Memory debugging tool diff --git a/custom/brew/fonts.Brewfile b/custom/brew/fonts.Brewfile new file mode 100644 index 0000000..1f2932c --- /dev/null +++ b/custom/brew/fonts.Brewfile @@ -0,0 +1,13 @@ +# Brewfile for fonts +# Nerd Fonts with icons and glyphs for terminals and editors + +# Popular coding fonts +cask "font-fira-code-nerd-font" +cask "font-jetbrains-mono-nerd-font" +cask "font-meslo-lg-nerd-font" +cask "font-hack-nerd-font" + +# Additional font options (uncomment to install) +# cask "font-ubuntu-nerd-font" +# cask "font-source-code-pro" +# cask "font-cascadia-code-nerd-font" diff --git a/custom/flatpaks/README.md b/custom/flatpaks/README.md new file mode 100644 index 0000000..c3a7e45 --- /dev/null +++ b/custom/flatpaks/README.md @@ -0,0 +1,95 @@ +# Flatpak Preinstall Integration + +This directory contains Flatpak preinstall configuration files that will be copied into your custom image at `/etc/flatpak/preinstall.d/`. + +## What is Flatpak Preinstall? + +Flatpak preinstall is a feature that allows system administrators to define Flatpak applications that should be installed on first boot. These files are read by the Flatpak system integration and automatically install the specified applications. + +## How It Works + +1. **During Build**: Files in this directory are copied to `/etc/flatpak/preinstall.d/` in the image +2. **On First Boot**: After user setup completes, the system reads these files and installs the specified Flatpaks +3. **User Experience**: Applications appear automatically after first login + +## Important: Installation Timing + +**Flatpaks are NOT included in the ISO or container image.** They are downloaded and installed after: +- User completes initial system setup +- Network connection is established +- First boot process runs `flatpak preinstall` + +This means: +- The ISO remains small and bootable offline +- Users need an internet connection after installation +- First boot may take longer while Flatpaks download and install +- This is NOT an offline ISO with pre-embedded applications + +## File Format + +Each file uses the INI format with `[Flatpak Preinstall NAME]` sections: + +```ini +[Flatpak Preinstall org.mozilla.firefox] +Branch=stable + +[Flatpak Preinstall org.gnome.Calculator] +Branch=stable +``` + +**Keys:** +- `Install` - (boolean) Whether to install (default: true) +- `Branch` - (string) Branch name (default: "master", commonly "stable") +- `IsRuntime` - (boolean) Whether this is a runtime (default: false for apps) +- `CollectionID` - (string) Collection ID of the remote, if any + +See: https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-preinstall + +## Usage + +### Adding Flatpaks to Your Image + +1. Edit [`default.preinstall`](default.preinstall) or create new `.preinstall` files in this directory +2. Add Flatpak references in INI format with `[Flatpak Preinstall NAME]` sections +3. Build your image - the files will be copied to `/etc/flatpak/preinstall.d/` +4. After user setup completes, Flatpaks will be automatically installed + +**Example Files in this directory:** +- [`default.preinstall`](default.preinstall) - Core applications from Bluefin + +### Finding Flatpak IDs + +To find the ID of a Flatpak: +```bash +flatpak search app-name +``` + +Or browse Flathub: https://flathub.org/ + +## Customization + +Edit the existing file or create new ones: +- **[`default.preinstall`](default.preinstall)** - Modify the default application list +- **Create new files:** + - `development.preinstall` - Development tools + - `gaming.preinstall` - Gaming applications + - `media.preinstall` - Media editing tools + +Each new `.preinstall` file will be automatically copied during the build process. See [`build/10-build.sh`](../../build/10-build.sh) for how files are copied. + +## Important Notes + +- Files must use the `.preinstall` extension +- Comments can be added with `#` +- Empty lines are ignored +- **Flatpaks are downloaded from Flathub on first boot** - not embedded in the image +- **Internet connection required** after installation for Flatpaks to install +- Installation happens automatically after user setup completes +- Users can still uninstall these applications if desired +- First boot will take longer while Flatpaks are being installed + +## Resources + +- [Flatpak Documentation](https://docs.flatpak.org/) +- [Flatpak Preinstall Reference](https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-preinstall) +- [Flathub](https://flathub.org/) diff --git a/custom/flatpaks/default.preinstall b/custom/flatpaks/default.preinstall new file mode 100644 index 0000000..7132a45 --- /dev/null +++ b/custom/flatpaks/default.preinstall @@ -0,0 +1,114 @@ +# Default Flatpak applications +# These will be installed on first boot +# Format: INI file with [Flatpak Preinstall NAME] groups +# See: https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-preinstall + +# Web Browsers +[Flatpak Preinstall org.mozilla.firefox] +Branch=stable + +[Flatpak Preinstall org.mozilla.Thunderbird] +Branch=stable + +# GNOME Core Applications +[Flatpak Preinstall org.gnome.Calculator] +Branch=stable + +[Flatpak Preinstall org.gnome.Calendar] +Branch=stable + +[Flatpak Preinstall org.gnome.Characters] +Branch=stable + +[Flatpak Preinstall org.gnome.Connections] +Branch=stable + +[Flatpak Preinstall org.gnome.Contacts] +Branch=stable + +[Flatpak Preinstall org.gnome.DejaDup] +Branch=stable + +[Flatpak Preinstall org.gnome.FileRoller] +Branch=stable + +[Flatpak Preinstall org.gnome.Firmware] +Branch=stable + +[Flatpak Preinstall org.gnome.Logs] +Branch=stable + +[Flatpak Preinstall org.gnome.Loupe] +Branch=stable + +[Flatpak Preinstall org.gnome.Maps] +Branch=stable + +[Flatpak Preinstall org.gnome.NautilusPreviewer] +Branch=stable + +[Flatpak Preinstall org.gnome.Papers] +Branch=stable + +[Flatpak Preinstall org.gnome.SimpleScan] +Branch=stable + +[Flatpak Preinstall org.gnome.TextEditor] +Branch=stable + +[Flatpak Preinstall org.gnome.Weather] +Branch=stable + +[Flatpak Preinstall org.gnome.baobab] +Branch=stable + +[Flatpak Preinstall org.gnome.clocks] +Branch=stable + +[Flatpak Preinstall org.gnome.font-viewer] +Branch=stable + +# Graphics & Media +[Flatpak Preinstall com.github.PintaProject.Pinta] +Branch=stable + +[Flatpak Preinstall com.github.rafostar.Clapper] +Branch=stable + +# System Utilities +[Flatpak Preinstall com.github.tchx84.Flatseal] +Branch=stable + +[Flatpak Preinstall com.mattjakeman.ExtensionManager] +Branch=stable + +[Flatpak Preinstall io.github.flattool.Warehouse] +Branch=stable + +[Flatpak Preinstall io.github.flattool.Ignition] +Branch=stable + +[Flatpak Preinstall io.missioncenter.MissionCenter] +Branch=stable + +[Flatpak Preinstall io.gitlab.adhami3310.Impression] +Branch=stable + +# Universal Blue Specific +[Flatpak Preinstall com.ranfdev.DistroShelf] +Branch=stable + +[Flatpak Preinstall io.github.kolunmi.Bazaar] +Branch=stable + +[Flatpak Preinstall page.tesk.Refine] +Branch=stable + +# GTK Themes (runtimes) +[Flatpak Preinstall org.gtk.Gtk3theme.adw-gtk3] +Branch=3.22 +IsRuntime=true + +[Flatpak Preinstall org.gtk.Gtk3theme.adw-gtk3-dark] +Branch=3.22 +IsRuntime=true diff --git a/custom/ujust/README.md b/custom/ujust/README.md new file mode 100644 index 0000000..30b5935 --- /dev/null +++ b/custom/ujust/README.md @@ -0,0 +1,240 @@ +# ujust - User-facing Just Commands + +This directory contains Just recipe files that will be installed into your custom image and made available to end users via the `ujust` command. + +## What is ujust? + +`ujust` is a command that allows users to run predefined tasks on their system. It's built on top of [just](https://github.com/casey/just), a command runner similar to `make` but designed for commands rather than builds. + +## How It Works + +1. **During Build**: All `.just` files in this directory are consolidated and copied to `/usr/share/ublue-os/just/60-custom.just` in the image +2. **After Installation**: Users run `ujust` to see available commands +3. **User Experience**: Simple command interface for system tasks + +## File Structure + +Create `.just` files in this directory with your custom commands: + +``` +custom/ujust/ +├── README.md # This file +├── custom-apps.just # Application installation commands +└── custom-system.just # System configuration commands +``` + +**Example Files in this directory:** +- [`custom-apps.just`](custom-apps.just) - Application installation commands (Brewfiles, Flatpaks, JetBrains Toolbox) +- [`custom-system.just`](custom-system.just) - System configuration commands (benchmarks, dev groups, maintenance) + +## Example Commands + +### Basic Command +```just +# Run a system maintenance task +run-maintenance: + echo "Running maintenance..." + sudo systemctl restart some-service +``` + +### Interactive Command with gum +```just +# Configure system setting +configure-thing: + #!/usr/bin/bash + source /usr/lib/ujust/ujust.sh + echo "Configure thing?" + OPTION=$(Choose "Enable" "Disable") + if [[ "${OPTION,,}" =~ ^enable ]]; then + echo "Enabling..." + # your enable logic + else + echo "Disabling..." + # your disable logic + fi +``` + +### Command with Group +```just +# Groups organize commands in ujust help +[group('Apps')] +install-brewfile: + brew bundle --file /usr/share/ublue-os/homebrew/development.Brewfile +``` + +## Best Practices + +### Naming Conventions +- Use lowercase with hyphens: `install-something` +- Use verb prefixes for clarity: + - `install-` - Install something + - `configure-` - Configure something pre-installed + - `setup-` - Install + configure + - `toggle-` - Enable/disable a feature + - `fix-` - Apply a fix or workaround + +### Command Structure +```just +# Brief description of what the command does +[group('Category')] +command-name: + #!/usr/bin/bash + # Use bash shebang for multi-line scripts + # Commands go here +``` + +### Error Handling +```just +install-something: + #!/usr/bin/bash + set -euo pipefail # Exit on error, undefined vars, pipe failures + # Your commands +``` + +### User Prompts +Use `gum` for interactive prompts (included in Universal Blue images): +```just +interactive-command: + #!/usr/bin/bash + source /usr/lib/ujust/ujust.sh # Provides Choose() and other helpers + OPTION=$(Choose "Option 1" "Option 2" "Cancel") + echo "You chose: $OPTION" +``` + +## Common Use Cases + +### 1. Installing Software via Brewfiles +```just +[group('Apps')] +install-dev-tools: + brew bundle --file /usr/share/ublue-os/homebrew/development.Brewfile +``` + +**See examples in [`custom-apps.just`](custom-apps.just)** for Brewfile shortcuts. + +### 2. System Configuration +```just +[group('System')] +configure-firewall: + #!/usr/bin/bash + sudo firewall-cmd --permanent --add-service=ssh + sudo firewall-cmd --reload +``` + +**See examples in [`custom-system.just`](custom-system.just)** for system configuration. + +### 3. Development Environment Setup +```just +[group('Development')] +setup-nodejs: + #!/usr/bin/bash + curl -fsSL https://fnm.vercel.app/install | bash + source ~/.bashrc + fnm install --lts +``` + +### 4. Maintenance Tasks +```just +[group('Maintenance')] +clean-containers: + podman system prune -af + podman volume prune -f +``` + +**See examples in [`custom-system.just`](custom-system.just)** for maintenance tasks. + +## Important: Package Installation + +**Do not install packages via dnf5/rpm in ujust commands.** Bootc images are immutable and package installation should happen at build time in [`build/10-build.sh`](../../build/10-build.sh). + +For runtime package installation, use: +- **Brewfiles** - Create shortcuts to Brewfiles in [`custom/brew/`](../brew/) +- **Flatpak** - Install Flatpaks for GUI applications +- **Containers** - Use toolbox/distrobox for development environments + +Example Brewfile shortcut (from [`custom-apps.just`](custom-apps.just)): +```just +[group('Apps')] +install-fonts: + brew bundle --file /usr/share/ublue-os/homebrew/fonts.Brewfile +``` + +## Available Helpers + +Universal Blue images include helpers in `/usr/lib/ujust/ujust.sh`: + +- `Choose()` - Present multiple choice menu +- `Confirm()` - Yes/no prompt +- Color variables: `${bold}`, `${normal}`, etc. + +## Testing Your Commands + +Test locally before committing: + +1. Build your image: `just build` (see [`Justfile`](../../Justfile)) +2. If on a bootc system: `sudo bootc switch --target localhost/finpilot:stable` +3. Reboot and test: `ujust your-command` + +Or test the just files directly: +```bash +just --justfile custom/ujust/custom-apps.just --list +just --justfile custom/ujust/custom-apps.just install-something +``` + +## Customization + +**Start by editing the example files:** +- **[`custom-apps.just`](custom-apps.just)** - Add your application installation commands +- **[`custom-system.just`](custom-system.just)** - Add your system configuration commands + +**Create new files** for different categories: +- `custom-gaming.just` - Gaming-related commands +- `custom-media.just` - Media editing workflows +- `custom-dev.just` - Development environment setups + +All `.just` files in this directory are automatically included. See [`build/10-build.sh`](../../build/10-build.sh) for the consolidation logic. + +## Groups for Organization + +Use groups to categorize commands: + +```just +[group('Apps')] +install-app: + echo "Installing app..." + +[group('System')] +configure-system: + echo "Configuring system..." + +[group('Development')] +setup-dev: + echo "Setting up dev environment..." +``` + +## Examples from Bluefin + +The included files provide starting examples: +- **[`custom-apps.just`](custom-apps.just)** - Application installation commands +- **[`custom-system.just`](custom-system.just)** - System configuration commands + +These files show how to: +- Create shortcuts to Brewfiles in [`custom/brew/`](../brew/) +- Install Flatpaks interactively +- Configure system settings +- Run maintenance tasks + +## Resources + +- [Just Manual](https://just.systems/man/en/) +- [Universal Blue Just Documentation](https://universal-blue.org/guide/just/) +- [Bluefin ujust Commands](https://docs.projectbluefin.io/administration) +- [gum Documentation](https://github.com/charmbracelet/gum) + +## Notes + +- Commands run with user privileges by default +- Use `sudo` or `pkexec` when root access needed +- Consider providing both install and uninstall options +- Test on a clean system before distributing +- Document any prerequisites or dependencies diff --git a/custom/ujust/custom-apps.just b/custom/ujust/custom-apps.just new file mode 100644 index 0000000..bb9bbc7 --- /dev/null +++ b/custom/ujust/custom-apps.just @@ -0,0 +1,80 @@ +# vim: set ft=make : +#################### +### custom-apps.just +#################### +## Example application installation commands +## These are simplified examples adapted from Bluefin + +# Install default applications via Homebrew +[group('Apps')] +install-default-apps: + #!/usr/bin/env bash + echo "Installing default applications via Homebrew..." + brew bundle --file /usr/share/ublue-os/homebrew/default.Brewfile + +# Install development tools via Homebrew +[group('Apps')] +install-dev-tools: + #!/usr/bin/env bash + echo "Installing development tools via Homebrew..." + brew bundle --file /usr/share/ublue-os/homebrew/development.Brewfile + +# Install fonts via Homebrew +[group('Apps')] +install-fonts: + #!/usr/bin/env bash + echo "Installing fonts via Homebrew..." + brew bundle --file /usr/share/ublue-os/homebrew/fonts.Brewfile + +# Install all Brewfiles at once +[group('Apps')] +install-all-brew: + #!/usr/bin/env bash + echo "Installing all applications from Brewfiles..." + brew bundle --file /usr/share/ublue-os/homebrew/default.Brewfile + brew bundle --file /usr/share/ublue-os/homebrew/development.Brewfile + brew bundle --file /usr/share/ublue-os/homebrew/fonts.Brewfile + +# Install JetBrains Toolbox for managing JetBrains IDEs +[group('Apps')] +install-jetbrains-toolbox: + #!/usr/bin/env bash + echo "Installing JetBrains Toolbox..." + pushd "$(mktemp -d)" + echo "Fetching latest version..." + curl -sSfL -o releases.json "https://data.services.jetbrains.com/products/releases?code=TBA&latest=true&type=release" + BUILD_VERSION=$(jq -r '.TBA[0].build' ./releases.json) + DOWNLOAD_LINK=$(jq -r '.TBA[0].downloads.linux.link' ./releases.json) + CHECKSUM_LINK=$(jq -r '.TBA[0].downloads.linux.checksumLink' ./releases.json) + echo "Installing JetBrains Toolbox ${BUILD_VERSION}" + curl -sSfL -O "${DOWNLOAD_LINK}" + curl -sSfL "${CHECKSUM_LINK}" | sha256sum -c + tar zxf jetbrains-toolbox-"${BUILD_VERSION}".tar.gz + mkdir -p $HOME/.local/share/JetBrains/ToolboxApp/ + mv jetbrains-toolbox-"${BUILD_VERSION}"/* $HOME/.local/share/JetBrains/ToolboxApp/ + popd + echo "Launching JetBrains Toolbox..." + $HOME/.local/share/JetBrains/ToolboxApp/bin/jetbrains-toolbox + +# Shortcut for install-jetbrains-toolbox +[group('Apps')] +jetbrains-toolbox: + @ujust install-jetbrains-toolbox + +# Install a Flatpak application from Flathub +[group('Apps')] +install-flatpak APP_ID: + #!/usr/bin/bash + echo "Installing {{ APP_ID }} from Flathub..." + flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak install -y flathub {{ APP_ID }} + +# Example: Install VSCode via Flatpak +[group('Apps')] +install-vscode: + ujust install-flatpak com.visualstudio.code + +# Example: Install GIMP via Flatpak +[group('Apps')] +install-gimp: + ujust install-flatpak org.gimp.GIMP diff --git a/custom/ujust/custom-system.just b/custom/ujust/custom-system.just new file mode 100644 index 0000000..bcf4b8f --- /dev/null +++ b/custom/ujust/custom-system.just @@ -0,0 +1,92 @@ +# vim: set ft=make : +####################### +### custom-system.just +####################### +## Example system configuration commands +## These are simplified examples adapted from Bluefin + +# Run a system benchmark (requires stress-ng from Homebrew) +[group('System')] +benchmark: + #!/usr/bin/env bash + source /usr/lib/ujust/ujust.sh + if ! type -P "stress-ng" &>/dev/null ; then + echo "stress-ng is not installed." + if command -v brew &>/dev/null; then + if gum confirm "Install stress-ng via Homebrew?" ; then + brew install stress-ng + brew link stress-ng + else + exit 0 + fi + else + echo "Please install stress-ng to run benchmarks." + exit 1 + fi + fi + echo 'Running a 1 minute benchmark...' + pushd $(mktemp -d) > /dev/null + stress-ng --matrix 0 -t 1m --times + popd > /dev/null + +# Configure docker and libvirt groups for development +[group('System')] +configure-dev-groups: + #!/usr/bin/pkexec bash + CURRENT_USER="{{ `id -un` }}" + echo "Adding $CURRENT_USER to docker and libvirt groups..." + + # Ensure groups exist in /etc/group + for group in docker libvirt; do + if ! grep -q "^$group:" /etc/group; then + echo "Adding $group to /etc/group" + grep "^$group:" /usr/lib/group | tee -a /etc/group > /dev/null + fi + usermod -aG $group $CURRENT_USER + done + + echo "Groups configured. Log out and back in for changes to take effect." + +# Example toggle command with user choice +[group('System')] +toggle-example-feature: + #!/usr/bin/bash + source /usr/lib/ujust/ujust.sh + echo "This is an example toggle command." + echo "Current status: [check your status here]" + OPTION=$(Choose "Enable" "Disable" "Cancel") + case "$OPTION" in + "Enable") + echo "Enabling feature..." + # Add your enable logic here + ;; + "Disable") + echo "Disabling feature..." + # Add your disable logic here + ;; + "Cancel") + echo "No changes made." + ;; + esac + +# Clean up container images and volumes +[group('Maintenance')] +clean-containers: + #!/usr/bin/bash + echo "Cleaning up Podman containers, images, and volumes..." + podman system prune -af + podman volume prune -f + echo "Cleanup complete!" + +# Update system and reboot if needed +[group('Maintenance')] +update-and-reboot: + #!/usr/bin/bash + source /usr/lib/ujust/ujust.sh + echo "Updating system..." + sudo bootc upgrade + if gum confirm "Reboot now to apply updates?"; then + systemctl reboot + else + echo "Reboot later to apply updates." + fi diff --git a/iso/disk.toml b/iso/disk.toml new file mode 100644 index 0000000..7478400 --- /dev/null +++ b/iso/disk.toml @@ -0,0 +1,3 @@ +[[customizations.filesystem]] +mountpoint = "/" +minsize = "20 GiB" diff --git a/iso/iso.toml b/iso/iso.toml new file mode 100644 index 0000000..4c25a4a --- /dev/null +++ b/iso/iso.toml @@ -0,0 +1,31 @@ +# Anaconda ISO Installer Configuration +# This configuration creates a bootable ISO image for installing your custom bootc image. +# +# The installer will guide users through disk partitioning and basic system setup, +# then automatically switch to your custom image on first boot. + +[customizations.installer.kickstart] +contents = """ +%post +# After installation, switch to the custom image from this repository +# IMPORTANT: Update this URL to match your repository (ghcr.io/USERNAME/REPO:stable) +bootc switch --mutate-in-place --transport registry ghcr.io/USERNAME/REPO:stable +%end +""" + +[customizations.installer.modules] +# Enable standard Anaconda installer modules for a guided installation experience +enable = [ + "org.fedoraproject.Anaconda.Modules.Storage", # Disk partitioning and formatting + "org.fedoraproject.Anaconda.Modules.Runtime", # Basic runtime configuration + "org.fedoraproject.Anaconda.Modules.Network", # Network configuration + "org.fedoraproject.Anaconda.Modules.Security", # Security settings + "org.fedoraproject.Anaconda.Modules.Services", # System services + "org.fedoraproject.Anaconda.Modules.Users", # User account creation + "org.fedoraproject.Anaconda.Modules.Timezone" # Timezone selection +] + +# Disable subscription management (not needed for community images) +disable = [ + "org.fedoraproject.Anaconda.Modules.Subscription", +] diff --git a/iso/rclone/README.md b/iso/rclone/README.md new file mode 100644 index 0000000..5a38947 --- /dev/null +++ b/iso/rclone/README.md @@ -0,0 +1,128 @@ +# Rclone Configuration Directory + +This directory contains example rclone configuration files for uploading ISO images to various cloud storage providers. + +## Available Configurations + +- **cloudflare-r2.conf** - Cloudflare R2 (S3-compatible, zero egress fees) +- **aws-s3.conf** - Amazon S3 (highly reliable, standard pricing) +- **backblaze-b2.conf** - Backblaze B2 (affordable, low egress fees) +- **sftp.conf** - SFTP/SSH upload to any server +- **scp.conf** - SCP upload to any server + +## How to Use + +### 1. Choose Your Storage Provider + +Select the configuration file that matches your preferred storage provider. Each file contains: +- Setup instructions +- Required GitHub secrets +- Provider-specific configuration options + +### 2. Set Up GitHub Secrets + +For the ISO build workflow to upload files, you need to configure GitHub secrets: + +1. Go to your repository on GitHub +2. Navigate to **Settings** → **Secrets and variables** → **Actions** +3. Click **New repository secret** +4. Add the secrets required by your chosen provider (listed in the config file) + +### 3. Choose Your Upload Method + +The build-disk.yml workflow supports two upload methods: + +#### Method A: Using rclone configs (Recommended) + +This method uses the configuration files in this directory: + +```yaml +# In .github/workflows/build-disk.yml, the workflow will: +# 1. Read the config from this directory +# 2. Substitute secrets automatically +# 3. Upload using rclone +``` + +To use this method, specify which config to use when triggering the workflow. + +#### Method B: Direct environment variables (Legacy) + +The workflow also supports direct environment variable configuration for backward compatibility. + +### 4. Triggering the Workflow + +The `build-disk.yml` workflow is triggered manually: + +1. Go to **Actions** tab in your repository +2. Select **Build disk images** workflow +3. Click **Run workflow** +4. Select the platform (amd64 or arm64) +5. Enable **Upload to cloud storage** if you want to upload the ISO + +## Configuration File Format + +All configuration files use the rclone INI format with placeholders for secrets: + +```ini +[remote-name] +type = provider_type +access_key_id = ${SECRET_NAME} +secret_access_key = ${ANOTHER_SECRET} +``` + +The workflow automatically replaces `${SECRET_NAME}` with the corresponding GitHub secret value. + +## Customizing Configurations + +You can modify these configuration files to suit your needs: + +1. Edit the configuration file for your provider +2. Update the secret names in `${...}` placeholders +3. Add the corresponding secrets to your GitHub repository +4. Commit the changes + +**Important:** Never commit actual credentials or secrets to the repository. Always use `${SECRET_NAME}` placeholders and GitHub secrets. + +## Provider Comparison + +| Provider | Setup Complexity | Cost | Egress Fees | Notes | +|----------|------------------|------|-------------|-------| +| Cloudflare R2 | Medium | $ | Free | Best for frequent downloads | +| AWS S3 | Medium | $$ | $$ | Most reliable, global reach | +| Backblaze B2 | Easy | $ | $ | Good balance of price/features | +| SFTP/SCP | Medium | Free* | Free | Requires your own server | + +*Requires existing server infrastructure + +## Troubleshooting + +### "Permission denied" errors +- Check that your access keys are correct +- Verify IAM permissions (for AWS) +- Ensure the bucket exists and is accessible + +### "Endpoint not found" errors +- Verify the endpoint URL is correct +- Check region settings +- For Cloudflare R2, ensure you're using the correct Account ID + +### Upload fails silently +- Enable workflow debug logging in GitHub Actions +- Check that secrets are properly set +- Verify the rclone config syntax + +## Additional Resources + +- [Rclone Documentation](https://rclone.org/docs/) +- [GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) +- [Cloudflare R2 Documentation](https://developers.cloudflare.com/r2/) +- [AWS S3 Documentation](https://docs.aws.amazon.com/s3/) +- [Backblaze B2 Documentation](https://www.backblaze.com/b2/docs/) + +## Need Help? + +If you encounter issues: +1. Check the workflow logs in the Actions tab +2. Review the rclone documentation for your provider +3. Ask in the [Universal Blue Discord](https://discord.gg/WEu6BdFEtp) +4. Post in the [Universal Blue Forums](https://universal-blue.discourse.group/) diff --git a/iso/rclone/aws-s3.conf b/iso/rclone/aws-s3.conf new file mode 100644 index 0000000..c1831b2 --- /dev/null +++ b/iso/rclone/aws-s3.conf @@ -0,0 +1,29 @@ +# AWS S3 Configuration Example +# +# Amazon S3 is a highly reliable object storage service. +# +# Setup Instructions: +# 1. Create an AWS account +# 2. Create an S3 bucket in your desired region +# 3. Create an IAM user with S3 access permissions +# 4. Generate access keys for the IAM user +# 5. Set the following GitHub secrets in your repository: +# - AWS_ACCESS_KEY_ID: Your AWS Access Key ID +# - AWS_SECRET_ACCESS_KEY: Your AWS Secret Access Key +# - AWS_REGION: Your bucket region (e.g., us-east-1, eu-west-1) +# - AWS_BUCKET_NAME: Your S3 bucket name +# +# Note: S3 charges for both storage and egress bandwidth + +[aws-s3] +type = s3 +provider = AWS +# Access credentials - use GitHub secrets +access_key_id = ${AWS_ACCESS_KEY_ID} +secret_access_key = ${AWS_SECRET_ACCESS_KEY} +# Your bucket's region +region = ${AWS_REGION} +# ACL settings +acl = private +# Optional: Enable server-side encryption +# server_side_encryption = AES256 diff --git a/iso/rclone/backblaze-b2.conf b/iso/rclone/backblaze-b2.conf new file mode 100644 index 0000000..7fdab66 --- /dev/null +++ b/iso/rclone/backblaze-b2.conf @@ -0,0 +1,24 @@ +# Backblaze B2 Configuration Example +# +# Backblaze B2 is affordable cloud storage with low egress fees. +# +# Setup Instructions: +# 1. Create a Backblaze account +# 2. Create a B2 bucket in the Backblaze dashboard +# 3. Create an Application Key with read/write access to your bucket +# 4. Note your Application Key ID and Application Key +# 5. Set the following GitHub secrets in your repository: +# - B2_ACCOUNT_ID: Your Backblaze Account ID (also called Application Key ID) +# - B2_APPLICATION_KEY: Your Backblaze Application Key +# - B2_BUCKET_NAME: Your B2 bucket name +# +# Note: Backblaze charges for storage and egress over the free tier (1GB/day) + +[backblaze-b2] +type = b2 +# Account credentials - use GitHub secrets +account = ${B2_ACCOUNT_ID} +key = ${B2_APPLICATION_KEY} +# Optional: Specify the bucket directly in the remote name +# This allows using the remote without specifying bucket in commands +# endpoint = diff --git a/iso/rclone/cloudflare-r2.conf b/iso/rclone/cloudflare-r2.conf new file mode 100644 index 0000000..cf8cc93 --- /dev/null +++ b/iso/rclone/cloudflare-r2.conf @@ -0,0 +1,29 @@ +# Cloudflare R2 Configuration Example +# +# Cloudflare R2 is S3-compatible storage with zero egress fees. +# +# Setup Instructions: +# 1. Create a Cloudflare account and enable R2 +# 2. Create an R2 bucket in the Cloudflare dashboard +# 3. Generate API tokens from R2 > Manage R2 API Tokens +# 4. Copy your Account ID from the R2 dashboard +# 5. Set the following GitHub secrets in your repository: +# - R2_ACCOUNT_ID: Your Cloudflare Account ID +# - R2_ACCESS_KEY_ID: Your R2 Access Key ID +# - R2_SECRET_ACCESS_KEY: Your R2 Secret Access Key +# - R2_BUCKET_NAME: Your R2 bucket name +# +# The endpoint format is: https://.r2.cloudflarestorage.com + +[cloudflare-r2] +type = s3 +provider = Cloudflare +# Access credentials - use GitHub secrets +access_key_id = ${R2_ACCESS_KEY_ID} +secret_access_key = ${R2_SECRET_ACCESS_KEY} +# Endpoint format +endpoint = https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com +# Region is not used for R2 but some tools require it +region = auto +# ACL settings +acl = private diff --git a/iso/rclone/scp.conf b/iso/rclone/scp.conf new file mode 100644 index 0000000..51de1f9 --- /dev/null +++ b/iso/rclone/scp.conf @@ -0,0 +1,29 @@ +# SCP Configuration Example +# +# Upload ISO files using SCP (SSH Copy Protocol). +# This is similar to SFTP but uses the SCP protocol directly. +# +# Setup Instructions: +# 1. Ensure you have SSH access to your target server +# 2. Generate an SSH key pair if you haven't already +# 3. Add the public key to your server's ~/.ssh/authorized_keys +# 4. Set the following GitHub secrets in your repository: +# - SCP_HOST: Your server hostname or IP address +# - SCP_USER: Your SSH username +# - SCP_SSH_KEY: Your private SSH key (entire contents) +# - SCP_PORT: SSH port (default: 22) +# - SCP_PATH: Remote path to upload files (e.g., /var/www/isos) +# +# Note: SCP is simpler but less feature-rich than SFTP + +[scp] +type = sftp +# Server details - use GitHub secrets +host = ${SCP_HOST} +user = ${SCP_USER} +port = ${SCP_PORT:-22} +# Authentication - use key-based auth +key_pem = ${SCP_SSH_KEY} +# SCP-specific settings +# Use SCP protocol instead of SFTP +use_scp = true diff --git a/iso/rclone/sftp.conf b/iso/rclone/sftp.conf new file mode 100644 index 0000000..81e0035 --- /dev/null +++ b/iso/rclone/sftp.conf @@ -0,0 +1,30 @@ +# SFTP Configuration Example +# +# Upload ISO files to any server with SSH/SFTP access. +# +# Setup Instructions: +# 1. Ensure you have SSH access to your target server +# 2. Generate an SSH key pair if you haven't already +# 3. Add the public key to your server's ~/.ssh/authorized_keys +# 4. Set the following GitHub secrets in your repository: +# - SFTP_HOST: Your server hostname or IP address +# - SFTP_USER: Your SSH username +# - SFTP_SSH_KEY: Your private SSH key (entire contents) +# - SFTP_PORT: SSH port (default: 22) +# - SFTP_PATH: Remote path to upload files (e.g., /var/www/isos) +# +# Note: For password authentication, use SFTP_PASSWORD instead of SFTP_SSH_KEY + +[sftp] +type = sftp +# Server details - use GitHub secrets +host = ${SFTP_HOST} +user = ${SFTP_USER} +port = ${SFTP_PORT:-22} +# Authentication - use key-based auth (recommended) +key_pem = ${SFTP_SSH_KEY} +# Or use password authentication (less secure) +# pass = ${SFTP_PASSWORD} +# Optional: Skip host key verification (not recommended for production) +# skip_links = false +# set_modtime = true