GNOME Extensions on Immutable Linux

GNOME Extensions on Immutable Linux

GNOME Shell extensions on image-based, atomic-update Linux systems like Bluefin require a fundamentally different management approach compared to traditional Linux distributions. While GNOME extensions are a standard desktop customization mechanism, their intersection with bootc's read-only filesystem architecture creates distinct operational challenges. Understanding where extensions can be installed, how they persist across atomic updates, and how to troubleshoot compatibility issues is essential for Bluefin users seeking to customize their GNOME desktop.

Bluefin implements a container-based operating system where local package layering is locked by default and the base system is delivered as complete images via bootc's transactional update mechanism. This architecture prevents traditional system-wide extension installation through package managers, making user-level extension directories the only practical approach. The good news: extensions installed in ~/.local/share/gnome-shell/extensions/ automatically persist across all system updates, rebases, and upgrades because user home directories remain mutable while the base system stays pristine.

This article provides comprehensive guidance on managing GNOME Shell extensions within Bluefin's architecture, covering installation methods, troubleshooting common issues (including real-world examples from the Bluefin community), update compatibility management, and best practices for maintaining a stable customized desktop on an image-based system.

Understanding GNOME Shell Extensions

GNOME Shell extensions are JavaScript-based modules that become part of GNOME Shell itself once loaded. Unlike plugins in other systems that interact through stable APIs, extensions are more accurately described as "patches, not plugins"—they can modify or replace any part of GNOME Shell's behavior, from adding UI elements to completely redesigning window management.

Extension Architecture

Extensions have full access to GNOME Shell's layered architecture:

Since GNOME 45, extensions use ESModules with relative paths for extension modules (import * as Utils from './utils.js') and resource:// URIs for GNOME Shell modules (resource:///org/gnome/shell/js/).

Extension Structure

Every extension must export a subclass of the base Extension class implementing:

Each extension requires a metadata.json file specifying:

{
  "uuid": "example@gjs.guide",
  "name": "Example Extension",
  "description": "What this extension does",
  "shell-version": ["45", "46", "47"],
  "url": "https://github.com/username/repo"
}

The shell-version field lists supported GNOME Shell versions. For GNOME 40+, only the major version is required (e.g., "45"); earlier versions required major and minor components (e.g., "3.38").

Version Compatibility and Breakage

GNOME Shell has no stable extension API by design—the system must remain generalized to give developers maximum freedom. This means extensions can break when GNOME Shell updates. GNOME 40+ re-enabled strict version checking due to significant architectural changes, so GNOME Shell only loads extensions that explicitly declare support for the current version.

Extensions break for several reasons:

Extension Architecture on Immutable Systems

The fundamental architectural difference between traditional and image-based Linux distributions determines how GNOME extensions must be managed.

bootc Filesystem Layout

Bluefin uses bootc for transactional, image-based operating system updates delivered via OCI container images. At runtime, the base userspace is not running in a container—systemd acts as pid1 normally with no outer process.

Container image files are read-only by default with the composefs backend. With composefs enabled (strongly recommended), /usr is not different from /—they are part of the same read-only image. All top-level directories like /usr and /opt are lifecycled with the container image and appear as read-only in the deployed system.

Persistent User Directories

Three key directories have different persistence behaviors:

/etc - Mutable Persistent State
Contains mutable persistent state by default with 3-way merge semantics across upgrades. Merges changes between default configuration in /usr/etc and admin modifications in /etc.

/var - Persistent Data
Content persists by default and is shared across deployments. Content in /var in the container image acts like Docker VOLUME /var—unpacked only from the initial image, not updated from subsequent images to avoid rolling back databases and logs.

User Home Directories - Fully Persistent
User home directories under /home/ persist completely across all deployments, updates, and system changes. This makes ~/.local/share/gnome-shell/extensions/ the ideal location for extensions.

Extension Installation Locations

GNOME Shell recognizes two extension installation directories:

LocationWritablePersists Across UpdatesRecommended
~/.local/share/gnome-shell/extensions/✅ Yes✅ YesRecommended
/usr/share/gnome-shell/extensions/❌ No (read-only)⚠️ N/A (part of base image)❌ Not practical

User-level extensions in ~/.local are the only practical approach on Bluefin because:

System-level extensions in /usr/share would require either:

  1. Building custom container images (impractical for individual users)
  2. Temporarily unlocking package layering (breaks immutability guarantees)

Installation Methods

User-level installation is the standard approach for managing extensions on Bluefin.

Via GNOME Extensions Website

The simplest method uses extensions.gnome.org:

  1. Visit https://extensions.gnome.org/ in a web browser
  2. Install the browser integration (if prompted)
  3. Search for desired extensions
  4. Click the toggle switch to install directly to ~/.local/share/gnome-shell/extensions/
  5. Extensions activate immediately

The website automatically checks GNOME Shell version compatibility and only displays compatible extensions.

Via Extension Manager Application

Bluefin includes extension management tools:

# Open Extension Manager from the application menu
# Or launch from terminal:
gnome-extensions-app

The Extension Manager provides a graphical interface to:

Manual Installation

For extensions not available through the website:

# Create extensions directory if it doesn't exist
mkdir -p ~/.local/share/gnome-shell/extensions/

# Extract extension archive to directory matching its UUID
unzip extension.zip -d ~/.local/share/gnome-shell/extensions/extension-uuid@example.com/

# Restart GNOME Shell (or log out and back in)
# X11: Alt+F2, type 'r', press Enter
# Wayland: Log out and back in

# Enable the extension
gnome-extensions enable extension-uuid@example.com

The directory name must exactly match the uuid field in the extension's metadata.json.

Via gnome-extensions Command-Line Tool

The gnome-extensions command provides scriptable extension management:

# List all installed extensions
gnome-extensions list

# Show details about a specific extension
gnome-extensions show extension-uuid@example.com

# Enable an extension
gnome-extensions enable extension-uuid@example.com

# Disable an extension
gnome-extensions disable extension-uuid@example.com

# Uninstall an extension
gnome-extensions uninstall extension-uuid@example.com

# Reset extension to defaults
gnome-extensions reset extension-uuid@example.com

Bluefin locks local package layering by default via LockLayering=true in /etc/rpm-ostreed.conf, which prevents mutation of the base OSTree commit including package overlays.

While package layering can be temporarily enabled to install system extensions via RPM packages, this approach:

If absolutely necessary for temporary testing:

# Check current layering status
cat /etc/rpm-ostreed.conf | grep LockLayering

# Edit to enable layering (requires sudo)
sudo nano /etc/rpm-ostreed.conf
# Change: LockLayering=false

# Layer a package (example)
rpm-ostree install gnome-shell-extension-example
sudo reboot

# Restore pure image mode when done
rpm-ostree reset
sudo reboot

Running rpm-ostree reset and rebooting always restores the system to pure image mode, providing a recovery mechanism.

Recommendation: Use user-level installation exclusively unless you're building custom Bluefin container images.

Extension Management Tools

Extension Manager (GUI)

The Extension Manager application provides the most user-friendly interface:

GNOME Tweaks is included in the base Bluefin installation and also provides extension management under its "Extensions" section, though with fewer features than the dedicated Extension Manager.

gnome-extensions (CLI)

The command-line tool offers scriptable extension management:

# Get extension information in JSON format
gnome-extensions info extension-uuid@example.com --prefs

# Create a backup list of enabled extensions
gnome-extensions list --enabled > ~/enabled-extensions.txt

# Disable all extensions (useful for troubleshooting)
gnome-extensions list --enabled | while read ext; do
  gnome-extensions disable "$ext"
done

# Re-enable extensions from backup
cat ~/enabled-extensions.txt | while read ext; do
  gnome-extensions enable "$ext"
done

dconf for Extension Settings

Extension settings are stored in GNOME's configuration database:

# View all extension settings
dconf dump /org/gnome/shell/extensions/

# Backup extension settings
dconf dump /org/gnome/shell/extensions/ > ~/extension-settings.dconf

# Restore extension settings
dconf load /org/gnome/shell/extensions/ < ~/extension-settings.dconf

# Reset specific extension settings
dconf reset -f /org/gnome/shell/extensions/extension-name/

Since dconf data is stored in ~/.config/dconf/, settings automatically persist across system updates.

Troubleshooting

Extension Crashes and Automatic Disabling

GNOME Shell automatically disables all extensions after a crash as a built-in safety mechanism. This is expected behavior, not a bug—if a faulty extension causes a crash, disabling all extensions prevents the crash from recurring on the next session.

Symptoms:

Recovery Procedure:

The issue occurs occasionally and is documented in Bluefin administration:

  1. Open Extension Manager from the Logo Menu
  2. Manually re-enable desired extensions using toggle switches
  3. Extensions activate immediately without requiring logout
  4. Customize in phases: enable core extensions first, then add others to identify problematic extensions

Diagnostic Steps:

# Check GNOME Shell logs for crash information
journalctl -fexu gnome-shell

# View only errors and warnings
journalctl -p 3 -u gnome-shell --since today

# Restart GNOME Shell without logging out (X11 only)
# Alt+F2, type 'r', press Enter

# Kill GNOME Shell (Wayland - forces logout)
killall -3 gnome-shell

Prevention: There is no way to prevent all extension crashes since extensions have full access to modify GNOME Shell internals. Focus on using well-maintained extensions and understanding the recovery process.

Compatibility Issues After System Updates

Bluefin delivers updates as complete new images checked automatically every 6 hours. Major updates may include new GNOME Shell versions that break extension compatibility.

Example: GNOME 49 Temporary Update and AppIndicator Extension

Bluefin LTS briefly deployed GNOME 49 in late 2025, but reverted to GNOME 48.3 in March 2026 due to compatibility concerns. Users are currently on GNOME 48.3. This temporary update illustrated how GNOME version changes can affect extensions.

During the GNOME 49 deployment, the AppIndicator extension was disabled by default, causing system tray icons for background applications like Proton VPN to disappear. This was a known issue affecting multiple users.

Root Cause:
GNOME 49 introduced changes that affected system tray support. While Bluefin ships AppIndicator by default to provide system tray functionality, extensions were automatically turned off during the update.

Solution at the Time:

  1. Open Extension Manager
  2. Find the AppIndicator extension
  3. Toggle it on to re-enable
  4. Run ujust update to ensure latest extension compatibility fixes
  5. Restart affected applications

The user who reported this confirmed the simple fix: "The extensions were turned off after the update by default, so just turning them on via the Extension Manager worked."

Current Status: Bluefin LTS has reverted to GNOME 48.3, which provides better extension compatibility and stability. The system now includes version locks to prevent automatic upgrades to GNOME 49 until broader compatibility is established.

General Compatibility Troubleshooting

Check GNOME Shell Version:

gnome-shell --version

Verify Extension Compatibility:

  1. Visit the extension's page on https://extensions.gnome.org/
  2. Check the "Shell version compatibility" list
  3. Update extensions if newer versions support your GNOME version
  4. Disable incompatible extensions temporarily

After Major Updates:

# Update all system components
ujust update

# Check which extensions are enabled but potentially incompatible
gnome-extensions list --enabled

# Disable extensions known to have issues
gnome-extensions disable problematic-extension@example.com

# Test GNOME Shell stability
# If stable, re-enable extensions one by one

During Fedora Version Upgrades:

When Bluefin releases images based on newer Fedora versions (e.g., moving from Fedora 40 to 41), GNOME Shell versions typically change. Plan for:

SELinux Permission Issues

Bluefin uses SELinux in enforcing mode by default. Extension failures with permission errors may indicate SELinux denials.

Diagnose SELinux Issues:

# Check for SELinux denials related to GNOME Shell
sudo ausearch -m avc -ts recent | grep gnome-shell

# View SELinux denials in journal
sudo journalctl -xe | grep -i selinux

# Check SELinux status
sestatus

Temporary Testing (Not Recommended for Production):

# Switch GNOME Shell to permissive mode for testing
sudo semanage permissive -a gnome_shell_t

# Test extension functionality
# If it works, the issue is SELinux-related

# Remove permissive mode after testing
sudo semanage permissive -d gnome_shell_t

For persistent SELinux issues, consult Bluefin's community forums or file an issue describing the specific extension and denial messages.

Extension Removal and Reset

Complete Extension Removal:

# Via CLI
gnome-extensions uninstall extension-uuid@example.com

# Or manually
rm -rf ~/.local/share/gnome-shell/extensions/extension-uuid@example.com/

# Clear extension settings
dconf reset -f /org/gnome/shell/extensions/extension-name/

Disable All Extensions (Recovery Mode):

# Temporarily move extensions directory
mv ~/.local/share/gnome-shell/extensions ~/.local/share/gnome-shell/extensions.bak

# Restart GNOME Shell
killall -3 gnome-shell

# System will boot with no extensions
# Re-enable extensions selectively:
mv ~/.local/share/gnome-shell/extensions.bak ~/.local/share/gnome-shell/extensions
gnome-extensions enable safe-extension@example.com

How Atomic Updates Affect Extensions

Understanding Bluefin's update mechanism is essential for managing extension compatibility over time.

Update Delivery and Timing

Bluefin automatically checks for updates every 6 hours and applies them on the next reboot. Updates are delivered as complete system images—not individual packages—providing atomic rollback capabilities.

User-level extensions behavior during updates:

Check for pending updates:

# View bootc status
bootc status

# Check for available updates
ujust update

# View update history
rpm-ostree status

Bluefin Update Streams

Bluefin provides four release streams with different Fedora base versions and update cadences:

StreamFedora BaseGNOME VersionUpdate FrequencyExtension Stability
gts42Older, stableInfrequent⭐⭐⭐ Highest
stable43CurrentWeekly⭐⭐ High
latest43+Current/RecentDaily⭐ Moderate
beta44Pre-releaseFrequent⚠️ Testing

Choosing a stream for extension stability:

Switching streams:

# Switch to GTS for maximum stability
sudo bootc switch ghcr.io/ublue-os/bluefin:gts
sudo reboot

# Switch to stable (current recommended stream)
sudo bootc switch ghcr.io/ublue-os/bluefin:stable
sudo reboot

# Check current deployment
rpm-ostree status

Rebasing Between Bluefin Variants

When rebasing between Bluefin variants (e.g., standard to DX), user extensions persist automatically since they're stored in the home directory:

# Example: Switch from Bluefin to Bluefin-DX
sudo bootc switch ghcr.io/ublue-os/bluefin-dx:stable
sudo reboot

# Extensions remain installed and configured
# GNOME Shell version typically stays consistent within same stream

Rollback After Problematic Updates

If a system update breaks extensions or introduces GNOME Shell changes that cause instability:

# View deployment history
rpm-ostree status

# Roll back to previous deployment
rpm-ostree rollback
sudo reboot

# System boots into previous GNOME version
# Extensions that worked before will work again

Rollback is instant because bootc maintains previous deployments as separate filesystem trees.

Differences from Traditional Linux Distributions

Understanding these architectural differences clarifies why extension management differs on Bluefin:

Traditional Package-Based Distributions

System Architecture:

Extension Management:

Update Behavior:

Bluefin (Image-Based Distribution)

System Architecture:

Extension Management:

Update Behavior:

Key Advantage for Extensions:

On traditional systems, extension breakage could coincide with package conflicts, configuration drift, and system instability, making diagnosis difficult. On Bluefin:

Best Practices

1. Use User-Level Installation Exclusively

Never attempt package layering for extensions. User-level installation provides:

2. Maintain Extension Documentation

Keep a list of installed extensions for reproducibility:

# Create extension list
gnome-extensions list > ~/Documents/my-extensions.txt

# Document enabled extensions specifically
gnome-extensions list --enabled > ~/Documents/enabled-extensions.txt

# Add to version control or backup system

After system recovery or fresh installation, use this list to reinstall extensions manually from extensions.gnome.org.

3. Monitor GNOME Version Compatibility

Before applying major updates:

# Check current GNOME version
gnome-shell --version

# After update notification, check release notes
# Visit extensions.gnome.org for each extension
# Verify compatibility with new GNOME version

# Disable incompatible extensions preemptively
gnome-extensions disable incompatible-extension@example.com

4. Backup Extension Configuration

dconf settings persist automatically, but explicit backups enable recovery after system reinstallation:

# Backup all GNOME settings including extensions
dconf dump / > ~/Backups/gnome-settings-$(date +%Y%m%d).dconf

# Backup only extension settings
dconf dump /org/gnome/shell/extensions/ > ~/Backups/extension-settings.dconf

# Restore after system changes
dconf load /org/gnome/shell/extensions/ < ~/Backups/extension-settings.dconf

5. Test Extensions Before Committing to Updates

When Bluefin publishes updates:

  1. Check Bluefin release notes
  2. Note GNOME version changes
  3. Test extensions in a non-critical session if possible
  4. Disable known-problematic extensions before updating
  5. Keep previous deployment available for rollback

6. Use Stable, Well-Maintained Extensions

Prefer extensions with:

Avoid:

7. Implement Periodic Extension Audits

Quarterly maintenance routine:

# List all installed extensions
gnome-extensions list

# Review each extension:
# - Still needed?
# - Still maintained?
# - Compatible with current GNOME?

# Remove unused extensions
gnome-extensions uninstall unused-extension@example.com
rm -rf ~/.local/share/gnome-shell/extensions/unused-extension@example.com/

# Update extension list documentation
gnome-extensions list > ~/Documents/my-extensions.txt

8. Leverage Atomic Rollback

When experimenting with new extensions:

  1. Note current deployment before installing extensions
  2. Install and test new extensions
  3. If GNOME Shell becomes unstable, roll back:
    rpm-ostree rollback
    sudo reboot
    
  4. System returns to state before extension installation attempt

9. Participate in Bluefin Community

Bluefin's philosophy emphasizes "eliminating problems rather than documenting workarounds". When extensions cause issues:

Bluefin-Specific Considerations

GNOME Integration Philosophy

Bluefin implements GNOME integration with a focus on staying out of the user's way, providing a vanilla GNOME experience with thoughtful defaults. The project ships upstream tools rather than custom applications, which means:

Update Timing and Extension Preparation

Bluefin checks for updates every 6 hours automatically. Before each update check:

  1. Verify critical extensions are compatible with potential GNOME updates
  2. Keep backups of extension configurations
  3. Document extension list for quick recovery
  4. Monitor Bluefin community for update-related issues

No System-Wide Extension Distribution

Unlike some distributions that bundle GNOME extensions in their base images, Bluefin's design philosophy means:

Developer-Focused Extension Categories

Bluefin targets cloud-native development workflows, making certain extension categories particularly relevant:

System Monitoring & Resources:

Window Management:

Developer Productivity:

Integration & Services:

All should be installed at user-level and tested for compatibility with your Bluefin stream's GNOME version.

Recovery Procedures

If extensions cause GNOME Shell to become unusable:

Method 1: Disable Extensions via TTY

# Switch to TTY2: Ctrl+Alt+F2
# Login with your credentials

# Disable all extensions
gsettings set org.gnome.shell disable-user-extensions true

# Switch back to GUI: Ctrl+Alt+F1
# Login to GNOME Shell without extensions

# Re-enable extensions selectively via Extension Manager

Method 2: Move Extensions Directory

# From TTY or recovery mode
mv ~/.local/share/gnome-shell/extensions ~/.local/share/gnome-shell/extensions.bak

# Login to vanilla GNOME Shell
# Identify problematic extension
# Restore extensions except the problematic one

Method 3: System Rollback

# If system changes coincided with extension issues
rpm-ostree rollback
sudo reboot

# System returns to previous deployment
# Extensions that worked before will work again

bootc Image-Based Updates
Understanding bootc's transactional update mechanism provides context for why user-level extensions persist while system components update atomically.

OSTree Filesystem Semantics
The underlying technology for Bluefin's image-based architecture, explaining read-only roots and deployment management.

Flatpak Application Distribution
Bluefin's preferred method for installing GUI applications, complementing user-level extensions for desktop customization.

GNOME Shell Development
For users interested in creating their own extensions or understanding the extension architecture more deeply.

Bluefin Administration Guide
Comprehensive guide to managing Bluefin systems, including update streams, package management philosophy, and system maintenance.

References and Resources

Official Documentation

Community Resources

Key Source Files and Repositories

RepositoryPurposeRelevance
ublue-os/bluefinMain Bluefin system imagesBase system configuration affecting GNOME
projectbluefin/commonShared configuration modulesGNOME defaults and integration
GNOME/gnome-shellGNOME Shell source codeExtension API and architecture
GNOME/gnome-shell-extensionsOfficial GNOME extensionsReference implementations

Troubleshooting References


This article reflects Bluefin's architecture as of 2025. For the latest information, consult the official Bluefin documentation.