Initial commit
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
[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
|
||||
@@ -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.
|
||||
@@ -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: [
|
||||
'(?<justName>.+?)\\s:=\\s(?:env\\(.+?,\\s)?["\'](?<packageName>\\S+?):(?<currentValue>\\S+?)@(?<currentDigest>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\\+=\\((?<packageName>[a-zA-Z0-9._:/-]+):?(?<currentValue>\\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"],
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,4 @@
|
||||
cosign.key
|
||||
_build_*
|
||||
output
|
||||
_build-*/**
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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 "{}" ';'
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
Executable
+65
@@ -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!"
|
||||
Executable
+68
@@ -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!"
|
||||
Executable
+92
@@ -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"
|
||||
@@ -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
|
||||
Executable
+30
@@ -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"
|
||||
}
|
||||
+16
@@ -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!
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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/)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
[[customizations.filesystem]]
|
||||
mountpoint = "/"
|
||||
minsize = "20 GiB"
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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/)
|
||||
@@ -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
|
||||
@@ -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 =
|
||||
@@ -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://<account-id>.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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user