Files
fluffy-pancake/AGENTS.md
T
2026-02-17 17:27:26 +01:00

1109 lines
36 KiB
Markdown

# 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**
```
<type>[optional scope]: <description>
```
## 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: <paste entire contents of cosign.key>
# 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: `<type>[scope]: <description>`
- 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