Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/factory/IMPROVEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,4 @@ Reference: [`docs/skills/bonedigger.md`](../skills/bonedigger.md)
| 2026-06-12 | CI/OCI | chunkah cadence signal: added `bootc-build/apply-pkg-intervals` composite action + `reusable-pkg-cadence` self-updating workflow. Sets `user.update-interval` xattrs on all RPM-owned files before rechunking so chunkah packs by update frequency instead of blind. Bootstrap data from live images (bluefin 1,826 pkgs, lts 1,489). Self-updates after each Execute Release — no PRs, direct commit. | actions#210, bluefin#537, bluefin-lts#181 |
| 2026-06-12 | Build | Package arrays extracted to TOML manifests in bluefin and bluefin-lts. New `read-packages` Python helper (tomllib stdlib) consumed with readarray. bluefin 03-packages.sh: 198→86 lines (-56%). bluefin-lts 20-packages.sh: 76→58 (-24%), 10-packages-image-base.sh: 122→97 (-20%). Python orchestration refactor filed as bluefin#538. | bluefin#539, bluefin-lts#182 |
| 2026-06-12 | CI | Factory-wide automerge reliability fix: dropped `--auto` from `reusable-renovate-automerge.yml` (works without branch protection), switched all 53 `projectbluefin/actions` SHA pins to `@v1` managed tag across dakota/bluefin/bluefin-lts (propagation delay was the root cause of the June 13 outage), added `no-sha-pins-for-internal-actions` pre-commit hook to enforce `@v1` going forward, fixed `build.yml` missing `testing` in push trigger (blocking :testing image updates), and added `renovate-automerge.yml` to common with mergeraptor bypass token. | projectbluefin/actions@cfcf98a, dakota#830, bluefin#552, bluefin-lts#204 |
| 2026-06-12 | CI | Factory-wide automerge reliability: dropped `--auto` from `reusable-renovate-automerge.yml`, migrated all 53 `projectbluefin/actions` SHA pins to `@v1` across dakota/bluefin/bluefin-lts (propagation delay was root cause of June 13 outage), added `no-sha-pins-for-internal-actions` pre-commit enforcement, fixed `build.yml` missing `testing` push trigger (blocking :testing image updates), added `renovate-automerge.yml` to common with mergeraptor bypass token. | projectbluefin/actions@cfcf98a, dakota#830, bluefin#552, bluefin-lts#204 |
| 2026-06-12 | CI | Factory-wide automerge reliability: dropped `--auto` from `reusable-renovate-automerge.yml`, migrated all 53 `projectbluefin/actions` SHA pins to `@v1` across dakota/bluefin/bluefin-lts (propagation delay was root cause of June 13 outage), added `no-sha-pins-for-internal-actions` pre-commit enforcement, fixed `build.yml` missing `testing` push trigger (blocking :testing image updates), added `renovate-automerge.yml` to common with mergeraptor bypass token. | projectbluefin/actions@cfcf98a, dakota#830, bluefin#552, bluefin-lts#204 |
2 changes: 1 addition & 1 deletion docs/skills/containerfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The final `ctx` stage is a scratch image — it contains only the file tree that
**The wallpaper source is still `ghcr.io/ublue-os/bluefin-wallpapers-gnome`.**

```dockerfile
COPY --from=ghcr.io/ublue-os/bluefin-wallpapers-gnome:latest@sha256:e4d74fa741ce9ff03a6a60440a58c31cef6c0fc145182357d243580ba239f810 / /out/bluefin/usr/share
COPY --from=ghcr.io/ublue-os/bluefin-wallpapers-gnome:latest@sha256:... / /out/bluefin/usr/share
```

This is a build-time `COPY --from` image reference, not a runtime registry path. The production image tree lives in `ghcr.io/projectbluefin/`, but the wallpaper artwork still originates from the `ublue-os` artwork registry. This is intentional — the wallpapers are upstream artwork, not projectbluefin-owned infrastructure.
Expand Down
4 changes: 1 addition & 3 deletions docs/skills/devmode.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
---
name: devmode
description: "ujust devmode wizard — installs developer stack in-place on any Bluefin image. No -dx rebase. Covers tool selection, progress bar, group setup, and marker file."
metadata:
type: procedure
description: Turn on Developer Mode wizard for Bluefin. In-place DX setup via ujust devmode — no -dx image rebase required. Covers tool selection, progress bar, group config, and marker file.
---

# devmode — Turn on Developer Mode
Expand Down
241 changes: 189 additions & 52 deletions system_files/bluefin/usr/share/ublue-os/just/system.just
Original file line number Diff line number Diff line change
Expand Up @@ -43,41 +43,191 @@ bluefin-cli:
devmode:
@ujust toggle-devmode

# Toggle between Bluefin and the Developer Experience
# Set up the Developer Experience
[group('System')]
toggle-devmode:
#!/usr/bin/env bash
set -e
JUSTFILE="{{ justfile() }}"
IMAGE_INFO_FILE="${IMAGE_INFO_FILE:-/usr/share/ublue-os/image-info.json}"
IMAGE_NAME="$(jq -rc '."image-name"' "${IMAGE_INFO_FILE}")"
IMAGE_BASE_NAME="$(cut -f1 -d\- <<< "${IMAGE_NAME}")"
# Use the booted deployment reference so stable-daily images get the correct tag
# Strip transport prefix (ostree-image-signed:docker://, ostree-unverified-registry:, etc.)
CURRENT_IMAGE="$(rpm-ostree status -b --json | jq -rc '.deployments[0]."container-image-reference"' | sed -E 's|^.*://||; s|^[a-z-]+:||')"
set +e

if grep -q -E -e "dx$" <<< "${IMAGE_NAME}" ; then
gum confirm "Would you like to disable developer mode?" || exit 0
echo "Rebasing to a non developer image"
NEW_IMAGE="$(sed "s/\-dx//" <<< $CURRENT_IMAGE)"
pkexec bootc switch --enforce-container-sigpolicy "${NEW_IMAGE}"
exit 0
DEVMODE_MARKER="${HOME}/.config/bluefin/devmode"

gum style \
--foreground 212 --border-foreground 212 --border double \
--align center --width 60 --padding "1 4" \
"Turn on Developer Mode"
echo ""

if [ -f "${DEVMODE_MARKER}" ]; then
gum confirm "Developer mode is already set up. Add more tools?" || exit 0
echo ""
fi

gum confirm "Would you like to enable developer mode?" || exit 0
echo "Rebasing to a developer image"
NEW_IMAGE="$(sed "s/${IMAGE_BASE_NAME}/${IMAGE_BASE_NAME}-dx/" <<< "${CURRENT_IMAGE}")"
pkexec bootc switch --enforce-container-sigpolicy "${NEW_IMAGE}"
if gum confirm "Do you want to also install the default development flatpaks?" ; then
ADD_DEVMODE=1 ujust install-system-flatpaks 0 1
CHOICES="$(gum choose --no-limit \
--height 22 \
--selected-prefix "◉ " \
--unselected-prefix "○ " \
--cursor "▸ " \
--selected " Docker, Podman Desktop" \
"── Docker ───────────────────────────────────" \
" Docker" \
"── Podman ───────────────────────────────────" \
" Podman Desktop" \
"── Virtualization ───────────────────────────" \
" Virtual Machines (virt-manager + QEMU)" \
" Lima (lightweight VMs via KVM)" \
" incus (system containers)" \
"── IDE ──────────────────────────────────────" \
" VS Code" \
" VSCodium" \
" Antigravity" \
" Zed" \
" JetBrains Toolbox" \
"── CLI Editors ──────────────────────────────" \
" Neovim" \
" Helix" \
" vim" \
" micro" || true)"

# _dx_wants: test whether a choice was selected
_dx_wants() { echo "${CHOICES}" | grep -q "$@"; }

# Build install summary
WILL_INSTALL=("devcontainer CLI")
_dx_wants -F " Docker" && WILL_INSTALL+=("docker + compose + lazydocker + dive")
_dx_wants "Podman Desktop" && WILL_INSTALL+=("Podman Desktop")
_dx_wants "Virtual Machines" && WILL_INSTALL+=("virt-manager + QEMU")
_dx_wants -F " Lima" && WILL_INSTALL+=("Lima")
_dx_wants "incus" && WILL_INSTALL+=("incus")
_dx_wants -F " VS Code" && WILL_INSTALL+=("VS Code")
_dx_wants "VSCodium" && WILL_INSTALL+=("VSCodium")
_dx_wants "Antigravity" && WILL_INSTALL+=("Antigravity")
_dx_wants "Zed" && WILL_INSTALL+=("Zed")
_dx_wants "JetBrains" && WILL_INSTALL+=("JetBrains Toolbox")
_dx_wants "Neovim" && WILL_INSTALL+=("Neovim")
_dx_wants "Helix" && WILL_INSTALL+=("Helix")
_dx_wants -F " vim" && WILL_INSTALL+=("vim")
_dx_wants "micro" && WILL_INSTALL+=("micro")

echo ""
SUMMARY_BODY=$(
gum style --bold "Ready to install:"
for item in "${WILL_INSTALL[@]}"; do
echo " ● ${item}"
done
)
gum style \
--border rounded --border-foreground 212 \
--padding "1 3" --width 50 \
"${SUMMARY_BODY}"
echo ""

gum confirm "Install now?" || exit 0
echo ""

INSTALL_LIBVIRT=0
INSTALL_LIMA=0
INSTALL_INCUS=0
INSTALL_DOCKER=0
_dx_wants -F " Docker" && INSTALL_DOCKER=1
_dx_wants "Virtual Machines" && INSTALL_LIBVIRT=1
_dx_wants -F " Lima" && INSTALL_LIMA=1
_dx_wants "incus" && INSTALL_INCUS=1

# Taps are silent/fast — run before the progress bar
if _dx_wants -E "VS Code|VSCodium|Antigravity|JetBrains"; then
brew tap ublue-os/tap 2>/dev/null || true
fi
if gum confirm "Do you want to install extra monospace fonts?" ; then
brew bundle install --file=/usr/share/ublue-os/homebrew/fonts.Brewfile
if _dx_wants "Zed"; then
brew tap ublue-os/experimental-tap 2>/dev/null || true
fi
if gum confirm "Do you want to set up the VM stack (virt-manager, QEMU, Windows 11 support)?" ; then
ujust setup-vms

# Build install queue for the progress bar
# _dx_add: append one entry to both name and command arrays
_dx_add() { DX_NAMES+=("$1"); DX_CMDS+=("$2"); }
DX_NAMES=("devcontainer CLI")
DX_CMDS=("brew install devcontainer")
[ "${INSTALL_DOCKER}" -eq 1 ] && _dx_add "Docker" "brew install docker docker-compose lazydocker dive"
_dx_wants "Podman Desktop" && _dx_add "Podman Desktop" "flatpak install --system --noninteractive flathub io.podman_desktop.PodmanDesktop"
[ "${INSTALL_LIBVIRT}" -eq 1 ] && _dx_add "Virtual Machines" "flatpak install --system --noninteractive flathub org.virt_manager.virt-manager org.virt_manager.virt_manager.Extension.Qemu"
[ "${INSTALL_LIMA}" -eq 1 ] && _dx_add "Lima" "brew install lima"
[ "${INSTALL_INCUS}" -eq 1 ] && _dx_add "incus" "brew install incus"
_dx_wants -F " VS Code" && _dx_add "VS Code" "brew install --cask ublue-os/tap/visual-studio-code-linux"
_dx_wants "VSCodium" && _dx_add "VSCodium" "brew install --cask ublue-os/tap/vscodium-linux"
_dx_wants "Antigravity" && _dx_add "Antigravity" "brew install --cask ublue-os/tap/antigravity-linux"
_dx_wants "Zed" && _dx_add "Zed" "brew install --cask ublue-os/experimental-tap/zed-linux"
_dx_wants "JetBrains" && _dx_add "JetBrains Toolbox" "brew install --cask ublue-os/tap/jetbrains-toolbox-linux"
_dx_wants "Neovim" && _dx_add "Neovim" "brew install nvim"
_dx_wants "Helix" && _dx_add "Helix" "brew install helix"
_dx_wants -F " vim" && _dx_add "vim" "brew install vim"
_dx_wants "micro" && _dx_add "micro" "brew install micro"

# Progress bar helpers — Unicode block bar embedded in gum spin title.
# gum spin clears its own line on success, so we print the checkmark ourselves.
# Result looks like the Bubble Tea package-manager example:
# ✓ devcontainer CLI
# ⠙ ████████████░░░░░░░░░░░░░░░░ 2/7 Docker
_dx_bar() {
local done=$1 total=$2 width=28 bar="" i
local filled=$(( done * width / total ))
for ((i=0; i<filled; i++)); do bar+="█"; done
for ((i=0; i<(width-filled); i++)); do bar+="░"; done
printf '%s' "${bar}"
}
_dx_step() {
local idx=$1 total=$2 name=$3; shift 3
local bar; bar="$(_dx_bar "${idx}" "${total}")"
local digits=${#total} label
printf -v label "%*d/%*d" "${digits}" "$((idx+1))" "${digits}" "${total}"
if gum spin --spinner dot \
--title " ${bar} ${label} ${name}" -- "$@"; then
gum style --foreground 42 "✓ ${name}"
else
gum style --foreground 196 "✗ ${name}" >&2
return 1
fi
}

N=${#DX_NAMES[@]}
for i in "${!DX_NAMES[@]}"; do
_dx_step "${i}" "${N}" "${DX_NAMES[${i}]}" \
bash -c "${DX_CMDS[${i}]}" || exit 1
done

# Post-install: configure libvirt session URI (instant — no spinner needed)
if [ "${INSTALL_LIBVIRT}" -eq 1 ]; then
mkdir -p "${HOME}/.config/libvirt"
grep -q "uri_default" "${HOME}/.config/libvirt/libvirt.conf" 2>/dev/null || \
echo 'uri_default = "qemu:///session"' >> "${HOME}/.config/libvirt/libvirt.conf"
fi
echo "Use ujust dx-group to add your user to the correct groups and complete the installation after rebooting into the development image"

CURRENT_USER="$(id -un)"
GROUPS_TO_ADD="dialout"
[ "${INSTALL_DOCKER}" -eq 1 ] && GROUPS_TO_ADD="${GROUPS_TO_ADD} docker"
[ "${INSTALL_LIBVIRT}" -eq 1 ] && GROUPS_TO_ADD="${GROUPS_TO_ADD} libvirt"
[ "${INSTALL_INCUS}" -eq 1 ] && GROUPS_TO_ADD="${GROUPS_TO_ADD} incus-admin"
pkexec bash -c "
append_group() {
local g=\"\$1\"
if ! grep -q \"^\${g}:\" /etc/group 2>/dev/null; then
grep \"^\${g}:\" /usr/lib/group 2>/dev/null | tee -a /etc/group > /dev/null || true
fi
}
for g in ${GROUPS_TO_ADD}; do
append_group \"\${g}\"
usermod -aG \"\${g}\" ${CURRENT_USER} 2>/dev/null || true
done
"

mkdir -p "${HOME}/.config/bluefin"
touch "${DEVMODE_MARKER}"
echo ""
gum style \
--foreground 2 --border-foreground 2 --border rounded \
--align center --width 60 --padding "1 4" \
"Developer Mode is on! Reboot to apply group changes."

# Configure docker, incus-admin, libvirt, serial permissions (standalone)

# Toggle between the stable and testing image channels
[group('System')]
Expand Down Expand Up @@ -110,42 +260,39 @@ toggle-testing:
fi
pkexec bootc switch --enforce-container-sigpolicy "${IMAGE_PATH}:${NEW_TAG}"

# Configure docker,incus-admin,libvirt, container manager, serial permissions
[group('System')]
dx-group:
#!/usr/bin/pkexec bash
append_group() {
local group_name="$1"
if ! grep -q "^$group_name:" /etc/group; then
echo "Appending $group_name to /etc/group"
grep "^$group_name:" /usr/lib/group | sudo tee -a /etc/group > /dev/null
if ! grep -q "^${group_name}:" /etc/group 2>/dev/null; then
echo "Appending ${group_name} to /etc/group"
grep "^${group_name}:" /usr/lib/group 2>/dev/null | tee -a /etc/group > /dev/null || true
fi
}

GROUPS_ADD=("docker" "incus-admin" "libvirt" "dialout")

for GROUP_ADD in "${GROUPS_ADD[@]}" ; do
append_group $GROUP_ADD
usermod -aG $GROUP_ADD {{ `id -un` }}
append_group "${GROUP_ADD}"
usermod -aG "${GROUP_ADD}" {{ `id -un` }} 2>/dev/null || true
done

echo "Reboot system and log back in to use docker, libvirt, incus, and serial connections."

# Set up the VM stack: virt-manager flatpak + QEMU extension (UEFI, TPM, USB passthrough)
# Idempotent — safe to run multiple times. Called by toggle-vms and toggle-devmode.
[group('System')]
setup-vms:
#!/usr/bin/env bash
set -euo pipefail
flatpak install --system --noninteractive flathub \
org.virt_manager.virt-manager \
org.virt_manager.virt_manager.Extension.Qemu
mkdir -p ~/.config/libvirt
if ! grep -q "uri_default" ~/.config/libvirt/libvirt.conf 2>/dev/null; then
echo 'uri_default = "qemu:///session"' >> ~/.config/libvirt/libvirt.conf
mkdir -p "${HOME}/.config/libvirt"
if ! grep -q "uri_default" "${HOME}/.config/libvirt/libvirt.conf" 2>/dev/null; then
echo 'uri_default = "qemu:///session"' >> "${HOME}/.config/libvirt/libvirt.conf"
fi
echo "VM stack ready. Launch Virtual Machine Manager from the app menu."
echo "Supports: Linux/Windows VMs, Windows 11 (UEFI + TPM), USB passthrough."
echo "VM stack ready. Supports: Linux/Windows VMs, Windows 11 (UEFI + TPM), USB passthrough."

# Toggle the VM stack on or off
[group('System')]
Expand All @@ -156,28 +303,18 @@ toggle-vms:
gum confirm "Remove the VM stack (virt-manager + QEMU)?" || exit 0
flatpak uninstall --system --noninteractive \
org.virt_manager.virt-manager \
org.virt_manager.virt_manager.Extension.Qemu || true
sed -i '/uri_default.*session/d' ~/.config/libvirt/libvirt.conf 2>/dev/null || true
org.virt_manager.virt_manager.Extension.Qemu 2>/dev/null || true
sed -i '/uri_default.*session/d' "${HOME}/.config/libvirt/libvirt.conf" 2>/dev/null || true
echo "VM stack removed."
else
gum confirm "Install the VM stack (virt-manager + QEMU)?" || exit 0
ujust setup-vms
just --justfile "{{ justfile() }}" setup-vms
fi

# Install system flatpaks for rebasers
# Install system flatpaks
[group('System')]
install-system-flatpaks $confirm="1" $dx_only="0":
install-system-flatpaks $confirm="1":
#!/usr/bin/env bash
if [[ "$(jq '."image-flavor"' /usr/share/ublue-os/image-info.json)" =~ dx ]] ; then
ADD_DEVMODE="${ADD_DEVMODE:-1}"
fi
if [ "${ADD_DEVMODE:-0}" == "1" ] ; then
if [ "$confirm" != 0 ] ; then
gum confirm "Install development flatpaks?" || exit 0
fi
brew bundle --file="${TARGET_FLATPAK_FILE:-/usr/share/ublue-os/homebrew/system-dx-flatpaks.Brewfile}"
fi
[ "$dx_only" == 1 ] && exit 0
if [ "$confirm" != 0 ] ; then
gum confirm "Install system flatpaks?" || exit 0
fi
Expand Down
2 changes: 1 addition & 1 deletion system_files/shared/usr/bin/ublue-image-info.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# shellcheck disable=2046
IMAGE_INFO_FILE="${IMAGE_INFO_FILE:-/usr/share/ublue-os/image-info.json}"
echo -n "$(jq -r '"\"(.["image-name"]):\(.["image-tag"])"' < "${IMAGE_INFO_FILE}")"
echo -n "$(jq -r '"\(.["image-name"]):\(.["image-tag"])"' < "${IMAGE_INFO_FILE}")"

if [[ "$(rpm-ostree status --booted)" =~ "signed" ]]; then
echo -n " 🔐"
Expand Down
Loading