A Shell Script that Configures the GNOME Desktop Programmatically

License

The jc-gnome-settings repository provides the jc-gnome-settings.sh script, which holds James Cherti’s settings to customize the GNOME desktop environment, including window management, notifications, desktop behavior, keyboard settings, and more, to enhance the user experience.

Requirements

  • gsettings

Usage

  1. Clone the repository:

    git clone https://github.com/jamescherti/jc-gnome-settings
  2. Navigate to the repository directory:

    cd jc-gnome-settings
  3. Run the script to configure GNOME:

    ./jc-gnome-settings.sh

Author and License

The jc-gnome-settings tool has been written by James Cherti and is distributed under terms of the MIT license.

Links

Other project by the same author:

  • jc-dotfiles @GitHub: A collection of UNIX/Linux configuration files. You can either install them directly or use them as inspiration your own dotfiles.
  • bash-stdops @GitHub: A collection of Bash helper shell scripts.
  • jc-firefox-settings @GitHub: Provides the user.js file, which holds settings to customize the Firefox web browser to enhance the user experience and security.
  • jc-gentoo-portage @GitHub: Provides configuration files for customizing Gentoo Linux Portage, including package management, USE flags, and system-wide settings.
  • jc-xfce-settings: GNOME customizations that can be applied programmatically.
  • watch-xfce-xfconf: A command-line tool that can be used to configure XFCE 4 programmatically using the xfconf-query commands displayed when XFCE 4 settings are modified.

A Shell Script that Automates XFCE Desktop Configuration

License

The jc-xfce-settings project provides the jc-xfce-settings.sh script, which holds James Cherti’s settings to customize the XFCE desktop environment, including window management, notifications, desktop behavior, keyboard settings, and more, to enhance the user experience.

(The jc-xfce-settings.sh script was created with the help of watch-xfce-xfconf)

Requirements

  • The XFCE Desktop Environment,
  • and xfconf-query utility that is part of XFCE.

Usage

  1. Clone the repository:

    git clone https://github.com/jamescherti/jc-xfce-settings
  2. Navigate to the repository directory:

    cd jc-xfce-settings
  3. Run the script to configure XFCE:

    ./jc-xfce-settings.sh

Features

  • Title Bar Customization: Simplifies button layout for easier window management.
  • Font and Display Settings: Enables anti-aliasing, hinting, and configures RGBA rendering.
  • File Manager (Thunar): Optimizes behavior for thumbnailing, single-click navigation, and directory-specific settings.
  • Keyboard Tweaks: Adjusts key repeat delay and rate for a smoother typing experience.
  • Notifications: Sets notification theme, position, and timeout duration.
  • Desktop Behavior: Disables unnecessary desktop icons and menus for a cleaner workspace.
  • Session Management: Disables session saving for a faster logout experience.
  • Window Management: Configures snapping, shadow effects, focus behavior, and workspace interactions.
  • Compositor Settings: Adjusts transparency and disables unneeded effects.

Author and License

The jc-xfce-settings tool has been written by James Cherti and is distributed under terms of the MIT license.

Links

Other project by the same author:

  • jc-dotfiles @GitHub: A collection of UNIX/Linux configuration files. You can either install them directly or use them as inspiration your own dotfiles.
  • bash-stdops @GitHub: A collection of Bash helper shell scripts.
  • jc-gnome-settings: GNOME customizations that can be applied programmatically.
  • jc-firefox-settings @GitHub: Provides the user.js file, which holds settings to customize the Firefox web browser to enhance the user experience and security.
  • jc-gentoo-portage @GitHub: Provides configuration files for customizing Gentoo Linux Portage, including package management, USE flags, and system-wide settings.

Ansible-role-flatpak, an Ansible Role that Installs and Configures Flatpak

License

The ansible-role-flatpak Ansible role installs and configures Flatpak, adds Flathub as a remote repository, and optionally manages Flatpak packages and updates. This role supports specific Linux distributions: Debian-based, Gentoo, and Arch Linux.

Requirements

  • Supported operating systems: Debian/Ubuntu (and their derivatives), Gentoo, or Arch Linux.
  • Ansible collections:
    • community.general

Role Variables

The following variables can be set to customize the role’s behavior:

Variable Description Default
flatpak_packages List of Flatpak packages to install. []
flatpak_update_script Whether to enable automatic daily updates and cleanup of Flatpak packages. false
flatpak_update_script_cmd_prefix Optional prefix command to run before the Flatpak update command (e.g., nice, ionice).
flatpak_update_script_cmd_suffix Optional suffix to append to the Flatpak update command. ‘>/dev/null’
flatpak_update_script_remove_unused Delete unused Flatpak packages after a successful update true
flatpak_update_script_script_path Path to the update script. ‘/etc/cron.daily/flatpak-update’
flatpak_install_desktop_portal Install the desktop portal. Values: ” (no desktop portal), “gtk”, or “gnome”. Can be a string or a list of values, such as [“gtk”, “gnome”] [‘gtk’]
flatpak_proxy Proxy settings for Flatpak (optional). Leave empty if not using a proxy.

Author and license

The ansible-role-flatpak role has been written by James Cherti and is distributed under terms of the MIT license.

Copyright (C) 2025-2026 James Cherti

Distributed under terms of the MIT license.

Links

How Breaking Windows 30 Years Ago Turned Me Into a Software Developer and Linux Professional

Every technology professional has an origin story. Mine began in 1995, centered around a Packard Bell corner computer running Windows 95.

This beige chassis dominating the desk instantly transports me back to the very beginning of my career. I do not just see outdated hardware; I feel a profound nostalgia for the quiet, uninterrupted hours I spent discovering the inner workings of operating systems and programming languages.

That computer was my gateway into the digital realm.

First programming language

The idea of computers controlling vehicles and medical devices remotely with artificial intelligence, as depicted in the science-fiction movies I watched on VHS at the time, encouraged me to explore programming.

As a teenager in 1995, I began learning to code using Visual Basic 4. Despite its constraints, Visual Basic 4 was an essential first step because its drag-and-drop interface and event-driven model allowed me to immediately see the results of my logic.

My first major project was a digital pet similar to a Tamagotchi, the global phenomenon of 1996. The basic mechanics functioned well, but it taught me about time-delta calculations when I tied the pet’s hunger decay to the processor speed instead of real-time intervals. During this period, I consumed my first books on programming, deepening my understanding of software development.

A significant challenge that forced me to learn the command-line…

In a misguided attempt to clear a few megabytes of disk space, I noticed a file named io.sys.

Assuming anything with such a short, cryptic name could not be important, I deleted it.

After rebooting the computer, the operating system failed to initialize. The screen displayed the error: “Non-system disk or disk error.” This was a cold-sweat lesson in operating system architecture.

MS-DOS and writing my first video game

The “Non-system disk or disk error” error forced me, for weeks, to boot the computer using a MS-DOS rescue disk. Using DOS forced me to read a book where I learned the command-line, and I soon discovered Quick Basic 4.5, which renewed my enthusiasm.

My programming journey took a significant leap forward when I discovered Quick Basic 4.5. I remember typing in a sample piece of code that switched the display mode to SCREEN 13, unlocking a specific graphical resolution in 256 colors. As simple, brightly colored circles and squares materialized on the monitor, a single thought instantly crossed my mind: “It’s great. I can make video games with this.” That exact moment is where my journey into game development started. I began with modest projects, writing small tools like an XOR encryption program to hide text files, before moving on to interactive entertainment. My very first game was a simple space evasion title where the player had to maneuver a little airplane to avoid a barrage of falling asteroids.

As my ambition grew, I realized the standard graphics commands were a bit too slow for the smooth action I wanted to achieve. This led me to discover external assembly libraries like Future Software and DirectQB, which opened the door to making Quick Basic 4.5 games that were far more complicated. With these new capabilities, I designed a full platform game built on a scrolling engine. I managed multiple visual layers drawn one behind the other to create a sense of depth. I had one layer dedicated to the static background and another for the solid level tiles. The active foreground layer contained the main character, alongside monsters, boss encounters, coins and other collectibles.

Following my time with Quick Basic, I transitioned to developing games in C using the DJGPP compiler and the Allegro library. This shift moved me closer to the hardware and introduced me to more sophisticated programming paradigms. This experience with C was important in preparing me for the rigorous demands of enterprise-level software development and Linux infrastructure management.

I switched to GNU/Linux

A key moment in my technological journey occurred in 1999 when I began using GNU/Linux, a transition that served as the natural evolution of my time with MS-DOS. My forced immersion in the DOS command line after the io.sys incident had changed how I interacted with computers, shifting my preference away from graphical interfaces and toward the precision of text-based environments.

I had grown to enjoy the granular control and the intellectual rigor of overcoming technical challenges. GNU/Linux offered the ultimate playground for this mindset, providing a transparent architecture where I could apply my growing skills in C/C++ and shell scripting to solve complex infrastructure problems.

Installing Linux in the late nineties was not for the faint of heart; it often involved, for example, manually calculating monitor refresh rates for the XFree86 configuration. My journey included experimenting with several distributions: Caldera OpenLinux, Mandrake, Debian, SuSE, Fedora, RedHat, Gentoo, Ubuntu, and Arch Linux.

I collected installation CDs like trading cards. Each version provided distinct insights, and the Linux community proved to be an invaluable resource, filled with individuals keen to share their knowledge and rescue me from my frequent configuration errors. During those early Linux days, getting hardware to work was a notorious rite of passage.

This transition shifted my entire focus toward system administration, infrastructure, and operating system development. I studied computer networking in depth, specializing in the field, which inevitably led to some memorable learning experiences. To make the machine serve my exact needs as a software developer and administrator, I wrote hundreds of C, C++, Python, Perl, PHP, Bash shell scripts, etc.

Alongside my Linux system administration and software development adventures, I fell down the rabbit hole of text editor customization. I initially used Vim, spending hours tweaking its configuration and writing my own extensions until it functioned as a complete IDE.

However, the true turning point was my eventual switch to Emacs, which stands as my favorite editor today. The learning curve was steep, especially when diving into Elisp to customize my setup. There were definitely weekends where I spent more hours tweaking my configuration file to make the syntax highlighting look absolutely perfect than I spent writing actual software. The transition from Vim to Emacs required a massive effort, as I spent countless hours diving into Elisp, another of my favorite programming languages, to reimplement in Elisp the Vim plugins and scripts I had previously developed. Learning Elisp allowed me to fully bend the editor to my will. There are many specific reasons why I ultimately left Vim behind to make Emacs my permanent digital home, but I will detail those arguments in a future article.

Professional

Following this, I spent over two decades as a software development and Linux infrastructure professional across multiple corporations. I might never have pursued this career path had it not been for the incident where I deleted io.sys! As I moved into enterprise environments, the foundational lessons learned from recovering a broken boot sequence evolved into architecting resilient Linux servers and developing software.

Conclusion

Reflecting on my shift from DOS and Windows 95, I realize the significant impact that hands-on problem-solving and continuous learning have had on my career. The technology landscape has transformed considerably over the years, yet the excitement of discovery remains the same. This journey has been about more than personal growth and technical skills; it is also about the connections I have made and the supportive community around me. My experience highlights the dynamic nature of technology, where deleting a system file is a learning opportunity, and every new interest can lead to significant discoveries.

Related links

update-iptables – A low-level Linux firewall for advanced users

License: GPL v3

The update-iptables script implements a firewall for managing network traffic and routing.

It supports a modular configuration model through drop-in scripts located in /etc/update-iptables.d/. Each file is a shell script executed sequentially during firewall initialization.

This low-level firewall script is intended for Linux system administrators who require precise control over packet states, network address translation, and custom routing chains. Rules are defined directly through iptables without the abstraction layers commonly introduced by modern firewall management tools.

If this low-level firewall proves useful, please support the project by ⭐ starring update-iptables on GitHub, helping more developers discover it.

Requirements

  • bash
  • iptables (iptables, ip6tables, and iptables-save)
  • Optional: diff and cmp

Installation

Install update-iptables system-wide with the following commands:

git clone https://github.com/jamescherti/update-iptables
cd update-iptables
sudo ./install.sh

Enable the firewall service at boot:

systemctl enable update-iptables

Usage

By default, update-iptables blocks all traffic, including input, output, and forwarding, except for connections on the loopback interface.

Adding Custom Rules

Custom rules can be added by creating a .rules script in the /etc/update-iptables.d/ directory. Files in this directory are sourced sequentially during firewall initialization, allowing modular and organized rule management.

Create a new file in /etc/update-iptables.d/ with a descriptive name, ending with .rules. For example:

sudo nano /etc/update-iptables.d/10-my-rules.rules

Add your iptables commands in the file. For example:

# Set the default policy of the INPUT, OUTPUT, and FORWARD chains to DROP. This
# establishes a default-deny firewall policy for both IPv4 and IPv6.
ui_set_drop_policy

# Drop any traffic with an INVALID state match and TCP packets with illogical
# flag combinations, such as SYN and FIN or SYN and RST being set
# simultaneously.
ui_drop_invalid

# Accept traffic belonging to already established connections or packets related
# to them. This rule ensures that once a connection has been permitted by a
# specific rule, all subsequent packets for that session are processed quickly
# and efficiently without re-evaluating the entire rule set.
#
# (Add UI_FORWARD if the system is acting as a router, gateway, or host for
# virtual machines and containers, such as Docker, libvirt, that require network
# access.)
ui_allow_established UI_INPUT UI_OUTPUT

# Allow all legitimate internal traffic on the 'lo' interface,
# which is required for local applications and services to communicate.
# This function also drops packets on non-loopback interfaces that spoof loopback
# IP addresses (127.0.0.0/8 and ::1/128) to protect the system from
# external manipulation and network pollution.
ui_allow_loopback

# Accept all incoming ICMP echo requests, also known as pings. Only the first
# packet will count as new, the others will be handled by the RELATED,
# ESTABLISHED rule. Since the computer is not a router, no other ICMP with
# state NEW needs to be allowed.
ui_input_allow_ping

# Permit outbound network traffic for a specific list of local system users.
# (Usernames that do not exist on the host are silently ignored.)
ui_allow_output_users systemd-timesync sockd proxy root alpm

# SSH
iptables -A UI_INPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

Reload the firewall to apply the new rules:

sudo systemctl restart update-iptables

The new rule will be integrated automatically, respecting the modular structure of the firewall.

Features

  • Low-level iptables control: Defines firewall rules directly using iptables and ip6tables without intermediate management layers.

  • Modular configuration: Supports drop-in rule scripts located in /etc/update-iptables.d/. Files with the .rules extension are sourced sequentially, allowing incremental and organized firewall configuration.

  • Stateful firewall rules: Uses connection tracking (conntrack) to manage NEW, ESTABLISHED, RELATED, and INVALID packet states.

  • IPv4 and IPv6 support: Applies rules consistently to both iptables and ip6tables.

  • Custom rule chains: Introduces dedicated chains (UI_INPUT, UI_OUTPUT, UI_FORWARD, UI_PREROUTING, UI_POSTROUTING) to isolate managed rules from system chains.

  • Per-user network policies: Allows outgoing traffic to be restricted or permitted based on the Unix user ID using the owner module.

  • Secure default policy: Uses restrictive default policies (DROP) until rules are successfully applied.

  • Automatic rule validation and rollback behavior: In case of failure during execution, all policies are locked down to DROP to avoid leaving the system in an insecure state.

  • Packet logging support: Optional logging chains record packets passing through firewall chains with rate limiting.

  • Spoofing and malformed packet protection: Drops packets with invalid connection states, suspicious TCP flag combinations, and spoofed source addresses.

  • Localhost protection rules: Ensures correct handling of loopback traffic while preventing spoofed loopback packets from external interfaces.

  • Rule diff inspection: Saves firewall rules before and after execution and optionally displays a diff when changes occur.

  • Verbose execution mode: Displays executed iptables commands when verbose mode is enabled.

  • Safe rule flushing: Supports cooperative flushing of managed chains or full firewall reset through command-line options.

License

Copyright (C) 2012-2026 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.

Links

ansible-role-auto-upgrade – An Ansible role that automates upgrading Linux operating systems

The ansible-role-auto-upgrade Ansible role automates regular upgrades of supported operating systems:

  • Debian-based systems (e.g., Ubuntu, Debian, Linux Mint). This role provides a simpler alternative to unattended-upgrades for applying system updates.

(In future versions, Arch Linux and Gentoo will also be supported.)

License

Copyright (c) 2025-2026 James Cherti.

Distributed under terms of the MIT license.

Do you like ansible-role-auto-upgrade?

Please star ansible-role-auto-upgrade on GitHub.

Links

ansible-role-reniced – An Ansible role that configures reniced on Debian and Ubuntu based operating systems

The ansible-role-reniced Ansible role configures reniced on Debian and Ubuntu based operating systems.

Customizations

When reniced_conf is defined, it is used as the configuration content.

Include the role using:

- name: Import role reniced
  when: ansible_facts.os_family == "Debian"
  ansible.builtin.import_role:
    name: reniced

Variables:

reniced_conf: |
  # high prio network services
  0 ^apache
  0 ^nfsd
  0 ^ntpd
  0 ^openvpn
  0 ^portmap
  0 ^ppp
  0 ^rpc.
  0 ^sshd

  # medium prio network services
  5 ^inn$
  5 ^mysqld

  # low prio network services
  15i ^amavisd-new
  15i ^clamd
  15 ^controlchan
  15 ^exim4
  15 ^freshclam
  15 ^innwatch
  12 ^mailman
  15 ^rc.news
  15i ^spamd

  # long running user processes (screen)
  3 ^irssi

  # test OOM settings
  o1 bash

Author and license

Copyright (C) 2024-2026 James Cherti.

Distributed under terms of the MIT license.

Links

The Importance of Backups: Why Failing to Create or Test Them Leads to Regret

Data loss is an inevitable reality in the digital age. It is not a matter of “if,” but “when.” Whether you’re managing critical infrastructure for a large enterprise or simply storing personal documents, photos, and legal files on your own computer, the risk of data loss is ever-present. Despite this, many people and organizations fail to prioritize backups until it is too late.

Throughout my career as an IT specialist, I have witnessed this problem repeatedly. The pattern is always the same: individuals and companies lose data, panic ensues, and attempts are made to recover what could have been easily protected with minimal cost and effort.

Data loss

On a daily basis, I encounter numerous instances of data loss across various sectors. Companies pay for bare-metal servers or cloud compute instances to run their production workloads, but when these systems crash, experience filesystem corruption, or are compromised by security breaches, the response is typically one of outrage. Support tickets are submitted with complaints that their data is lost, with no backups, no snapshots, and no contingency plan in place.

This issue is not exclusive to large enterprises. Consider personal devices. How many individuals store years of important tax records, legal documents, irreplaceable family photos, or critical work files on their laptops, with no backup strategy whatsoever? Hardware failure, malware attacks, accidental deletion, theft, and simple human error can result in irreversible data loss in an instant.

Some of the cases I handled…

The cases I handled professionally can be truly staggering. For example, I worked with a client paying tens of thousands of dollars per month for a bare-metal server to host highly sensitive data, yet they had no backup strategy in place. When hardware failure inevitably occurred, the result was outrage, threats of legal action, and attempts to assign blame. Unfortunately, without a backup policy, disaster recovery plan, and monitoring system in place, these situations are often beyond repair.

In this particular instance, I worked closely with the client to establish a comprehensive backup procedure. This involved selecting an appropriate backup solution adapted to their infrastructure, setting up regular backups, and ensuring the backups were stored securely and off-site. Additionally, we implemented a monitoring system to alert the client to any potential issues before they became critical. The result is now a much more resilient system, where data loss is no longer a risk, and the client has a clear disaster recovery plan in place.

To make matters worse, I’ve witnessed scenarios where the financial cost of data loss has reached tens of thousands of dollars per day. This is not a matter of insufficient resources. It’s a matter of misplaced priorities and negligence. For a fraction of the cost of their monthly expenses, these individuals and organizations could have secured a robust backup solution that would have prevented the catastrophe.

The reality is simple: backups are inexpensive. Data loss, on the other hand, is costly, stressful, and often irreversible. The barriers to implementing reliable, automated backup solutions today are negligible. Whether you choose cloud storage, external drives, remote servers, the tools are readily available, and there is no excuse for neglecting to protect your data.

Restores have to be tested

It is a common misconception that having a backup in place means data is secure, simply because a tool reports, “Your backups were successful.” However, the true test of a backup’s reliability occurs when it is needed most, during a restore attempt. This is when many realize that despite the “successful” alert, the backup may be incomplete, corrupted, or otherwise unusable.

It is concerning that many individuals in technical roles lack the necessary expertise to properly set up and manage infrastructure within an organization. While foundational knowledge is readily available through online resources, there is a tendency for some to set up systems with minimal effort, a few clicks, and the system is presumed to be functioning. However, the true measure of proficiency in IT infrastructure lies not in the ability to configure a system, but in the ability to resolve issues when things inevitably fail.

Conclusion

If you are reading this, I encourage you to take a moment to assess your backup strategy. If you do not have one, create one today. If you already have a backup system in place, ensure that it is functioning correctly. Whether you are managing a business with critical client data or simply safeguarding personal files on your laptop, the importance of backups cannot be overstated.

Feel free to share your own experiences with data loss, lessons learned, or helpful backup solutions in the comments.

Pathaction, a universal Makefile for your entire filesystem: Run rule-driven commands on any file or directory

License: GPL v3

The pathaction tool is a flexible command-line utility for running commands on files and directories. Pass a file path as an argument, and the tool handles the rest, whether you are working with code, media, or configurations.

Think of pathaction like a Makefile for any file or directory in the filesystem. It uses a .pathaction.yaml file to determine which command to run, and you can use Jinja2 templating to make those commands dynamic. You can also use tags to define multiple actions for the exact same file type. For example, you can set up one tag to run a script, another to debug it, and a third to run a linter.

This tool is built for software engineers who manage multiple projects across diverse environments. It eliminates the cognitive load of switching between different build tools, environment configurations, and deployment methods. Run a single unified command on any file and trust that it gets handled correctly.

If this tool helps your workflow, please show your support by ⭐ starring pathaction on GitHub and sharing it on your website, blog, Mastodon, Reddit, X, LinkedIn, or other social media platforms to help more Git users discover its benefits.

Example

You can execute a file with the following commands:

pathaction -t main file.py

Or:

pathaction -t edit another-file.jpg

The -t option specifies the tag, allowing you to apply a tagged rule.

Here is an example of what a .pathaction.yaml rule-set file looks like:

---
actions:
  - path_match: "*.py"
    tags: main
    command:
      - "python"
      - "{{ file }}"

  - path_match: "*.jpg"
    tags:
      - edit
      - show
    command: "gimp {{ file|quote }}"

There are many ways to match paths, including using regular expressions and MIME types. See below for more details.

Editor Plugins

Installation

Here is how to install pathaction using pip:

pip install --user pathaction

The pip command above will install the pathaction executable in the directory ~/.local/bin/.

(Python requirements: jinja2, schema, PyYAML.)

Optional Dependencies

The pathaction CLI offers optional dependencies that extend its functionality. These extras can be installed according to environment requirements.

  • Colored Terminal Output (colors): Installs colorama to provide consistent cross-platform ANSI color support. This enhances the readability of standard output and error messages.

    pip install "pathaction[colors]"
  • Custom Process Title (proctitle): Installs setproctitle to rename the running process from python to pathaction. This simplifies identification in system monitoring tools such as top, htop, and ps.

    pip install "pathaction[proctitle]"

To install both extras at once, use a comma-separated list:

pip install "pathaction[colors,proctitle]"

Getting Started

1. Authorize your working directory

By default, pathaction does not read rule-set files from arbitrary directories for security reasons. The allowed directories must be explicitly permitted. To allow pathaction to load rules from your projects folder and its subdirectories, run:

pathaction --allow-dir ~/projects

2. Create a rule-set file

Navigate to your project root and create a .pathaction.yaml file (e.g., ~/projects/my-app/.pathaction.yaml). Here is a basic example to run Python files:

---
actions:
  - path_match: "*.py"
    tags: main
    command:
      - "python"
      - "{{ file }}"

3. Execute the file

Now, instead of manually invoking the Python interpreter, you can pass the file directly to pathaction. It will read the rule, match the .py extension, and execute the command.

pathaction ~/projects/my-app/dir1/dir2/file.py

Because the default tag is main, pathaction automatically targets the block we just defined.

You can also specify the tag:

pathaction -t main ~/projects/my-app/dir1/dir2/file.py

Comprehensive Examples

The real value of pathaction comes from defining complex, multi-tag workflows across different file types. Here are detailed examples of how to configure .pathaction.yaml for various scenarios.

Example 1: Full Python and Bash Workflow

Instead of memorizing different tools and their command-line arguments, you can define them as actions.

---
actions:
  # Execute the Python script
  - path_match: "*.py"
    tags: main
    command:
      - "python"
      - "{{ file }}"

  # Run tests
  - path_match: "*.py"
    tags: test
    command: "pytest -v {{ file|quote }}"

  # Type checking
  - path_match: "*.py"
    tags: typecheck
    command: "mypy {{ file|quote }}"

  # Execute Bash shell scripts
  - path_match: "*.sh"
    tags:
      - main
    command: "bash {{ file|quote }}"

You can invoke these specific actions by passing the -t (tag) flag:

pathaction -t typecheck src/utils.py
pathaction -t test src/test_utils.py

Example 2: Managing Infrastructure with Ansible

You can set up rules to apply Ansible playbooks directly.

---
actions:
  - path_match: "*playbook.yml"
    tags: main
    command: "ansible-playbook -i inventory.ini {{ file|quote }}"

Example 3: C/C++ Compilation and Execution

You can use pathaction to compile files on the fly and put the output binary in the correct directory.

---
actions:
  # Compile C++ source code
  - path_match: "*.cpp"
    tags: build
    cwd: "{{ file|dirname }}"
    command: "g++ -Wall -O2 {{ file|quote }} -o {{ file|basename|replace('.cpp', '') }}"

  # Run the compiled binary
  - path_match: "*.cpp"
    tags: run
    cwd: "{{ file|dirname }}"
    command: "./{{ file|basename|replace('.cpp', '') }}"

Command-Line Arguments

The pathaction utility accepts several arguments to control execution behavior:

  • -t, --tag: Execute the action associated with this tag (default is main).
  • -b, --confirm-before: Prompt for confirmation before executing the defined action.
  • -a, --confirm-after: Ask the user if they want to execute the action again after it finishes (respects the confirm_after_timeout configuration).
  • -l, --list: List all the .pathaction.yaml configuration files that apply to the given file path. Useful for debugging rule resolution.
  • -d, --allow-dir: Permanently allow pathaction to execute rules from the provided directory and its subdirectories.
  • --disallow-dir: Revoke access and remove a specific directory from your allowed list.
  • --list-allowed-dirs: Print a list of all permanently allowed directories.

Configuration Guide (.pathaction.yaml)

The rule-sets cascade hierarchically. When you execute a file, pathaction looks for .pathaction.yaml in the file’s current directory, and then walks up the filesystem tree (parent directories) to find and merge all other .pathaction.yaml files. This loading behavior is similar to that of a .gitignore file. In case of conflicting rules or configurations, priority is given to the rule set that is located in the directory closest to the specified file.

Each rule defined in the rule-set file must include at least the matching rule and the command.

Match Methods

You can target files using various matching strategies. Every match method also has a corresponding _exclude variant (e.g., path_match_exclude) to explicitly ignore files that would otherwise match.

  • path_match: Standard glob pattern matching (e.g., *.py).
  • path_match_case: Case-sensitive glob pattern matching.
  • path_regex: Regular expression path matching (case-insensitive by default).
  • path_regex_case: Case-sensitive regular expression matching.
  • mimetype: Strict MIME type matching (e.g., text/x-python).
  • mimetype_match: Glob pattern matching for MIME types (e.g., text/*).
  • mimetype_regex: Regular expression matching for MIME types.

Action Configuration

An action block can include the following optional attributes:

  • tags: A string or list of strings indicating the tag names.
  • comment: An informative string describing what the action does.
  • timeout: An integer specifying the maximum execution time in seconds.
  • cwd: The current working directory for the command.
  • stdout: Redirect the standard output of the command to the specified file path.
  • stderr: Redirect the standard error of the command to the specified file path. (If both stdout and stderr point to the exact same file path, they are combined).
  • shell: Boolean indicating if the command should be executed within a shell environment.
  • command / list_commands: The command string/array to execute. These two are mutually exclusive.

Global Options

You can define an options block at the root of your .pathaction.yaml to specify execution defaults:

  • shell_path: The absolute path to the shell executable used when shell: true (defaults to the user’s login shell).
  • verbose: Enable verbose logging.
  • debug: Enable debug mode.
  • timeout: A global timeout constraint in seconds.
  • confirm_after_timeout: The timeout in seconds when waiting for user input during a --confirm-after prompt.
  • last: If set to true, pathaction stops loading configurations from higher parent directories.

Jinja2 Variables and Filters

Jinja2 Variables

Variable Description
{{ file }} Replaced with the full absolute path to the targeted file.
{{ cwd }} Refers to the current working directory of the matched action.
{{ env }} Represents the operating system environment variables (dictionary).
{{ pathsep }} Denotes the path separator (e.g., / on Linux, \ on Windows).

Jinja2 Filters

  • quote: Escapes a string for use as a shell argument by wrapping it in single quotes and escaping internal single quotes. This prevents shell injection vulnerabilities. Example: "/home/user/my file.txt" | quote evaluates to '/home/user/my file.txt'.
  • basename: Extracts the trailing filename or leaf component of a filesystem path. Example: "/home/user/src/main.py" | basename evaluates to "main.py".
  • dirname: Returns the parent directory portion of a filesystem path. Example: "/home/user/src/main.py" | dirname evaluates to "/home/user/src".
  • file_only_dirname: Returns the parent directory if the path is a file, or returns the path itself if it is already a directory.
  • realpath: Resolves all symbolic links, relative segments (like ..), and duplicate separators to return the canonical absolute path. Example: "/usr/bin/../local/bin/python" | realpath evaluates to "/usr/local/bin/python".
  • abspath: Converts a relative path into an absolute path by prefixing it with the current working directory. Example: "src/main.py" | abspath evaluates to "/home/user/project/src/main.py".
  • relpath: Computes the relative path between two directories.
  • joinpath: Combines one or more path segments using the system filesystem separator. Example: "/var/log" | joinpath("nginx", "error.log") evaluates to "/var/log/nginx/error.log".
  • joincmd: Converts an array of command-line tokens into a single properly escaped shell command string. Example: ["grep", "-i", "error log"] | joincmd evaluates to 'grep -i "error log"'.
  • splitcmd: Parses a raw shell command string into an array of distinct arguments while honoring quotation rules and escape sequences. Example: "git commit -m 'initial release'" | splitcmd evaluates to ["git", "commit", "-m", "initial release"].
  • expanduser: Replaces a leading tilde notation (~ or ~user) with the absolute path of the corresponding user home directory. Example: "~/config/tmux.conf" | expanduser evaluates to "/home/user/config/tmux.conf".
  • expandvars: Substitutes environment variables within a string matching $VARIABLE or ${VARIABLE} with their current active system values. Example: "$HOME/.config" | expandvars evaluates to "/home/user/.config".
  • shebang: Inspects a file and extracts the first line directly if it begins with an executable script prefix (#!). Example: "/home/user/script.sh" | shebang evaluates to "#!/usr/bin/env bash".
  • shebang_list: Extracts the shebang line from a file, discards the initial #! marker, and parses the remaining contents into a clean token array. Example: "/home/user/script.sh" | shebang_list evaluates to ["/usr/bin/env", "bash"].
  • shebang_quote: Extracts the shebang line from a file, strips the #! marker, and returns the runtime interpreter directive as a safely balanced, shell-quoted string. Example: "/home/user/script.sh" | shebang_quote evaluates to "/usr/bin/env bash".
  • which: Searches the system environment variable PATH to locate the absolute path of an executable binary. Raises an error if the binary cannot be found. Example: "emacs" | which evaluates to "/usr/bin/emacs".
  • startswith: Evaluates to true if the string starts with the given prefix.
  • endswith: Evaluates to true if the string ends with the given suffix.

Frequently Asked Questions

Does pathaction walk the filesystem from the current directory to the top in search of .pathaction.yaml ruleset files?

Pathaction walks from the directory containing the file passed to it and merges .pathaction.yaml rules from all allowed parent directories.

There is a security measure by default: loading rules is allowed only in directories that have been explicitly permitted using pathaction --allow-dir ~/dir/projects/, which enables access to ~/dir/projects/ and all its subdirectories. If the entire home directory is allowed with pathaction --allow-dir ~/, rules can be loaded from any directory within the home directory.

What are the differences between make and pathaction?

The make tool centers on targets and dependency tracking, making it good for compiling software based on file timestamps. In contrast, the pathaction tool acts as a universal file execution router. Passing a file path directly to Pathaction determines the correct command to run based on defined file extensions or patterns.

While make relies on project-specific files with strict syntax, Pathaction uses YAML files that cascade hierarchically across your filesystem. Much like how Git handles ignore files, Pathaction loads and merges all .pathaction.yaml rule-set files found in parent directories. This allows you to define rules in your home directory that can be overridden by specific settings within individual project folders.

For example, a Python script in ~/project_a can be routed to a local virtual environment, while a Python script in ~/project_a/project_b can trigger a Docker execution simply by defining different .pathaction.yaml files in those directories. Pathaction loads and merges all .pathaction.yaml ruleset files found in parent directories. This means that any rule in ~/project_a/project_b/.pathaction.yaml that does not match a file falls back to the rules defined in ~/project_a/.pathaction.yaml, similar to how Git handles .gitignore files.

What is the difference between Pathaction and a command such as find | xargs?

It is very different from find | xargs. The pathaction tool functions like a customizable, developer-focused xdg-open. It acts as the intelligent router that receives each file path and automatically determines the correct command to execute based on your defined rules. Just as xdg-open relies on rigid system MIME types to launch GUI applications, Pathaction uses your hierarchical .pathaction.yaml configurations and Jinja2 templating to dynamically run commands.

How is pathaction different from a shebang?

Shebangs are fine for basic execution, but they have limitations that Pathaction was built to address.

A shebang only defines how to execute a script. It cannot tell your system how to lint, format, debug, or test files. With pathaction, you can use tags. Passing pathaction -t main file.py executes it, while passing pathaction -t test file.py can run it through pytest.

How is pathaction different from xdg-open?

File associations such as xdg-open apply globally. Pathaction uses cascading YAML files similar to how Git handles .gitignore files. A Python script in ~/project_a can be routed to a local virtual environment, while a Python script in ~/project_a/project_b can trigger a Docker execution simply by defining different .pathaction.yaml files in those directories. Pathaction loads and merges all .pathaction.yaml ruleset files found in parent directories. This means that any rule in ~/project_a/project_b/.pathaction.yaml that does not match a file falls back to the rules defined in ~/project_a/.pathaction.yaml.

In addition to that, Pathaction uses Jinja2 templating, allowing you to dynamically build complex shell commands based on the file name, its parent directory, or environment variables.

How does the author use pathaction?

The author’s .pathaction.yaml rules function as a universal bridge across distinct software environments.

  • For Python, Bash, C, C++, and related languages, rules are defined to install dependencies, build projects, execute binaries, run test suites, and launch debuggers.
  • For Ansible, rules automatically upload playbooks to remote servers, execute them, and validate their results.
  • For Emacs, rules integrate file-based actions directly with editor workflows, enabling evaluation, compilation, or linting based on context.
  • For Vim, rules provide similar editor integration, allowing files to trigger build, run, or formatting actions without manual command construction.

Pathaction operates as an IDE-like action layer for the filesystem. Instead of embedding logic inside each editor or build system, actions are described declaratively and applied uniformly across tools.

The primary advantage is cognitive simplicity. There is no need to memorize complex command-line flags or tool-specific invocation patterns. A file is passed to Pathaction with a semantic tag such as main, install, or debug, and the corresponding rule determines how the operation is executed.

License

Copyright (c) 2021-2026 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Links

Plugins for editors:

  • pathaction.el (Emacs package): Executing the pathaction command-line tool directly from Emacs.
  • vim-pathaction (Vim plugin): Executing the pathaction command-line tool directly from Vim.

13 Useful GNOME Shell Extensions for a Better Desktop Experience (Available in the official Debian repositories or on the GNOME Extensions website for other distributions)

GNOME Shell offers a clean and modern UI, but it often lacks functionality desired by power users and those coming from other desktop environments.

GNOME Shell extensions provide a way to restore or add features to your desktop. In this article, you’ll explore some of the most useful GNOME Shell extensions available directly from the official Debian repositories via apt (available in Debian Bookworm and newer).

For users running distributions other than Debian, this article provides a link to the GNOME Shell Extensions page for each extension, allowing installation on any supported distribution.

Extension Management

The GNOME Shell Extension Prefs tool offers a graphical interface for enabling, disabling, and configuring GNOME Shell extensions. It can be installed using the following command:

sudo apt install gnome-shell-extension-prefs

After installation, GNOME Shell extensions can be enabled and configured using the gnome-extensions-app command.

Productivity

gnome-shell-extension-caffeine

The Caffeine extension prevents the system from locking the screen or entering suspend mode while it is active or when an application switches to fullscreen mode. This extension is useful during presentations, video playback, or gaming sessions, where uninterrupted screen activity is required.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-caffeine

System Monitoring

gnome-shell-extension-system-monitor

The system-monitor extension displays CPU, memory, network, and other metrics in real time on the top bar. Useful for developers, system administrators, and performance-conscious users.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-system-monitor

gnome-shell-extension-impatience

The Impatience extension decreases the duration of animations in GNOME Shell, resulting in a more responsive user interface.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-impatience

Panel and Dash Customization

gnome-shell-extension-dash-to-panel

The Dash To Panel extension integrates the top panel and dash into a unified taskbar, emulating the interface conventions commonly found in Windows and KDE environments.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-dash-to-panel

gnome-shell-extension-dashtodock

The Dash To Dock extension moves the dash out of the Activities view and docks it on the screen. Ideal for users preferring a macOS-style dock or persistent application launcher.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-dashtodock

gnome-shell-extension-hide-activities

The Hide Activities Button extension removes the “Activities” button from the top bar. It is useful for cleaner interfaces.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-hide-activities

gnome-shell-extension-top-icons-plus

The TopIcons Plus extension restores support for tray icons (i.e., systray) by displaying them in the top panel.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-top-icons-plus

UI Clean-Up and Space Saving

gnome-shell-extension-pixelsaver

The Pixelsaver extension integrates window title bars into the top panel for maximized windows, saving vertical space.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-pixelsaver

gnome-shell-extension-no-annoyance

The NoAnnoyance extension disables “Window is ready” notifications, which can be distracting and interrupt focus when multitasking across multiple applications.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-no-annoyance

gnome-shell-extension-autohidetopbar

The Hide Top Bar extension automatically hides the top bar, helping reduce visual clutter and maximize vertical screen space.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-autohidetopbar

Menus and Application Launching

gnome-shell-extension-arc-menu

The ArcMenu extension replaces the default application overview with a highly customizable start menu. Suitable for users preferring hierarchical or categorized navigation.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-arc-menu

Desktop Icon Support

gnome-shell-extension-desktop-icons-ng

The Desktop Icons NG extension restores desktop icon support (files, folders, and shortcuts), which was removed in newer GNOME versions. Supports drag-and-drop and right-click menus.

To install it on a Debian-based system, execute the following command:

sudo apt install gnome-shell-extension-desktop-icons-ng

Conclusion

These GNOME Shell extensions enable the transformation of GNOME into an efficient and personalized environment. All GNOME Shell extensions mentioned in this article are available through the official Debian repositories.

Additional GNOME Shell extensions, not included in the Debian repositories, can be found on the official GNOME Shell Extensions website.

Installing Arch Linux onto a separate partition from an existing Debian-based distribution (Ubuntu, Debian, Linux Mint…), without using the Arch Linux installation media

Installing Arch Linux typically begins with booting from official installation media. However, it is also possible to bootstrap an Arch Linux installation from within a running Debian-based system (Ubuntu, Debian, Linux Mint, etc.). This method is advantageous in environments where rebooting into live media is impractical or when remote installation is desired.

This article outlines a workflow for installing Arch Linux from a Debian-based system using pacman, pacstrap, arch-chroot, and pacman-key.

Prerequisites

Ensure your Debian system has the necessary tools to begin the installation process:

apt-get install arch-install-scripts pacman-package-manager archlinux-keyring makepkgCode language: plaintext (plaintext)

This command installs the Arch Linux bootstrap tools, makepkg, the pacman package manager, and required keyrings.

Configure the pacman keyring

Initialize the pacman keyring:

pacman-key --initCode language: plaintext (plaintext)

Install the latest Arch Linux keyring using pacman without resolving dependencies (to avoid conflicts with Debian packages):

pacman -S --nodeps archlinux-keyringCode language: plaintext (plaintext)

Replace the outdated Debian’s pacman keyrings with Arch’s:

cp /usr/share/pacman/keyrings/* /usr/share/keyrings/Code language: plaintext (plaintext)

Delete the archlinux-keyring pacman package:

pacman -Rsc archlinux-keyringCode language: plaintext (plaintext)

Populate the keyring again:

pacman-key --populate archlinuxCode language: plaintext (plaintext)

Configure pacman

Modify the /etc/pacman.d/mirrorlist file to include a valid Arch Linux mirror:

Server = http://mirror.csclub.uwaterloo.ca/archlinux/$repo/os/$archCode language: plaintext (plaintext)

Next, create the /etc/pacman.conf file with the following configuration:

[options]
HoldPkg = pacman glibc
Architecture = auto
CheckSpace
ParallelDownloads = 5
SigLevel = Required DatabaseOptional

[core]
Include = /etc/pacman.d/mirrorlist

[extra]
Include = /etc/pacman.d/mirrorlist

# [community]
# Include = /etc/pacman.d/mirrorlistCode language: plaintext (plaintext)

Prepare the installation target

Assuming you have an existing partition or logical volume prepared (e.g., /dev/vg1/arch), mount it:

mount /dev/vg1/arch arch

mkdir -p /mnt/arch/boot
mount -o bind /boot /mnt/arch/bootCode language: plaintext (plaintext)

Install the base system

Use pacstrap to install the base Arch system:

pacstrap /mnt/arch base sudo nanoCode language: plaintext (plaintext)

This command installs a minimal yet functional base system.

Chroot into the new environment

Finally, change root into the newly installed Arch system:

arch-chroot /mnt/archCode language: plaintext (plaintext)

From this point, you may proceed with system configuration as per a standard Arch Linux installation (e.g., locale, hostname, users, packages, bootloader, etc.).

Follow the official Arch Linux installation guide.

Conclusion

Bootstrapping Arch Linux from a Debian system is an efficient method to deploy Arch without the need for traditional installation media. This workflow is suited for advanced users managing systems remotely or automating deployments.

Related links

Bash shell: Interactive Menu to Insert any String from the Tmux Scrollback Buffer Into the Shell Prompt

Imagine you’re working in a tmux session, navigating logs, editing config files, or running commands, and you suddenly need to reuse a path, a variable name, or a keyword that appeared earlier on the tmux scrollback buffer. Instead of scrolling back or retyping it manually, you press Ctrl-n, type a few fuzzy letters, press enter, and the desired string from the tmux scrollback is instantly inserted at your cursor.

The tmux scrollback buffer is the internal history of terminal output that tmux maintains for each pane. It consists of the lines that have scrolled off the visible screen but are still accessible for review or processing.

This article presents a Bash function that scans the tmux scrollback buffer, lets you interactively select strings using fzf, and inserts the chosen string directly into the command line prompt. It offers a fast, context-aware mechanism for inline string insertion, allowing you to work efficiently without breaking your flow.

Requirements

Implementation using Bash functions and readline

Add the following code snippet to your ~/.bashrc to bind Ctrl-n for selecting strings from the tmux scrollback buffer using fzf, and inserting the selected string directly into the shell prompt:

#!/usr/bin/env bash
# Author: James Cherti
# License: MIT
# URL: https://www.jamescherti.com/tmux-autocomplete-fzf-fuzzy-insertion-scrollback/

__tmux_fzf_autocomplete__() {
  # Capture the last 100,000 lines from the tmux scrollback buffer, reverse
  # order, and extract strings
  tmux capture-pane -pS -100000 \
    |
    # Split input on spaces and newlines, remove duplicates while preserving
    # order, and keep only strings longer than 4 characters
    awk 'BEGIN { RS = "[ \t\n]" } length($0) > 4 && !seen[$0]++' \
    |
    # Invoke fzf for case-insensitive exact fuzzy matching, with results shown
    # in reverse order
    fzf --no-sort --exact +i --tac
}

__tmux_fzf_autocomplete_inline__() {
  local selected
  selected="$(__tmux_fzf_autocomplete__)"

  local before
  before="${READLINE_LINE:0:$READLINE_POINT}"

  local after
  after="${READLINE_LINE:$READLINE_POINT}"
  
  READLINE_LINE="${before}${selected}${after}"
  READLINE_POINT=$((READLINE_POINT + ${#selected}))
}

# Pressing Ctrl-n autocompletes from the Tmux scrollback buffer
bind -x '"\C-n": "__tmux_fzf_autocomplete_inline__"'Code language: Bash (bash)

Key components:

  • tmux capture-pane: Retrieves the scrollback content from the active tmux pane.
  • awk: Processes tokens, removes duplicates, and filters out strings shorter than 5 characters to produce a concise list.
  • fzf: Offers an interactive fuzzy interface for filtering and selecting the desired token. The --tac flag reverses the line order, prioritizing the most recent content.
  • bind: Inserts the selected token inline at the Bash prompt using a readline binding, activated by Ctrl-n. This binding does not interfere with typical command-line editing, preserves the existing prompt content, and updates the cursor position correctly.

Conclusion

The code snippet in this article enhances the Bash shell by turning the tmux scrollback buffer into a live source of contextual string insertion. Within tmux workflows, it removes the need to scroll through output, improving both speed and accuracy during command-line tasks.

Related Links

  • This function is included in the author’s .bashrc, which is available in the jc-dotfiles repository.

Linux: Setting the default GDM login monitor in a multi-monitor setup using GNOME display settings

If you’re using a multi-monitor setup with GDM (GNOME Display Manager) and the login screen consistently appears on the wrong monitor, this article presents a tested solution that can be applied either manually or automated, ensuring that the GDM monitor configuration matches that of your primary user setup.

The issue

Consider the following scenario: You have two monitors connected to an Nvidia graphics card, and despite setting the primary monitor correctly in GNOME, the GDM login screen still appears on the secondary monitor. Even if the secondary monitor is turned off, GDM continues to display the login prompt there, as it defaults to the wrong monitor. Additionally, the screen resolution and refresh rate are not configured as desired. For instance, if you have a 144Hz display and GDM uses a different refresh rate, you may experience annoying black flickering when logging in, as the refresh rate changes mid-session.

The solution

The login screen configuration for GDM can be influenced by copying the monitor layout from your GNOME user session.

This is done by copying the ~/.config/monitors.xml file from your user configuration to GDM’s configuration directory.

Step 1: Configure your display using GNOME

First, configure your display layout as desired using GNOME’s display settings:

gnome-control-center displayCode language: plaintext (plaintext)

Step 2: Copy the resulting monitor configuration to GDM’s configuration directory

Copy the resulting monitor configuration ~/.config/monitors.xml to legacy GDM’s configuration directory and to the new location (/etc/xdg/monitors.xml):

# Legacy location
sudo mkdir -p ~gdm/.config/
sudo install -o root -m 644 ~/.config/monitors.xml ~gdm/.config/

# New location
sudo install -o root -m 644 ~/.config/monitors.xml /etc/xdg/monitors.xmlCode language: Bash (bash)

(the shell will automatically interpret ~gdm as the home directory of the gdm user)

Step 3: Restart GDM

Restart GDM with the following command:

sudo systemctl restart gdmCode language: plaintext (plaintext)

How to automatically copy monitors.xml to the GDM configuration directory?

The copying of the monitors.xml file can be automated by defining a systemd service override for the GDM service.

First, create the following directory using:

sudo mkdir -p /etc/systemd/system/gdm.service.d/Code language: plaintext (plaintext)

Then, create the file /etc/systemd/system/gdm.service.d/override.conf with the following contents:

[Service]
ExecStartPre=/usr/bin/env bash -c 'FILE=/home/YOUR_USER/.config/monitors.xml && test -f "$$FILE" && install -d -o gdm -g gdm ~gdm/.config && install -o gdm -g gdm -m 644 "$$FILE" ~gdm/.config/monitors.xml && install -o root -g root -m 644 "$$FILE" /etc/xdg/monitors.xml || true'Code language: plaintext (plaintext)

Ensure to:

  • Replace YOUR_USER with your actual desktop user name.

Conclusion

Copying the GNOME display settings to the GDM configuration directory ensures that GDM adopts the same monitor layout as your GNOME session, causing the login screen to appear on your preferred monitor.

Configuring Linux on a ThinkPad T420s Laptop (Debian, Ubuntu, Linux Mint…)

ThinkPad T420s laptops, despite their age, remain reliable for web browsing, word processing, and various other tasks. Linux can breathe new life into such dated computers, allowing them to perform efficiently.

I configured one of these laptops for my son, and he is now able to do his homework using it. With proper configuration, ThinkPad laptops can achieve optimal performance and power management. This article provides a guide to configuring X11/Xorg, kernel parameters, firmware, fan control, and power management settings to optimize the ThinkPad t420s for modern use.

Some instructions in this article are specific to Debian/Ubuntu-based distributions, but they can easily be adapted to other distributions such as Red Hat, Fedora, Arch Linux, Gentoo, and others.

X11/Xorg

To ensure proper functionality of Intel graphics and avoid visual tearing, use the kernel’s built-in modesetting driver.

Create the configuration file /etc/X11/xorg.conf.d/30-intel.conf:

Section "Device"
  Identifier "Device0"
  Driver "modesetting"
  Option "TearFree" "True"
EndSection
Code language: plaintext (plaintext)

IMPORTANT: Ensure that the integrated graphics are set as the default video card in the ThinkPad BIOS.

Kernel parameters

On a Debian/Ubuntu based distribution, kernel parameters can be appended to the kernel command line in the bootloader configuration, typically in /etc/default/grub (for GRUB users):

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"Code language: plaintext (plaintext)

Here are some GRUB_CMDLINE_LINUX_DEFAULT parameters that may be useful:

  • If you experience issues with the backlight control (increasing and decreasing brightness), add the following kernel parameter: acpi_backlight=native
  • If you experience system freezes, try adding the following kernel parameter: i915.enable_dc=0 i915.enable_fbc=0

After modifying this file, update GRUB with:

sudo update-grubCode language: plaintext (plaintext)

Fan control

Without software to control the fan, it may run at maximum speed. To enable fan control, create the file /etc/modprobe.d/thinkpad_acpi.conf:

options thinkpad_acpi fan_control=1Code language: plaintext (plaintext)

After that, install zcfan. On a Debian/Ubuntu based distribution:

sudo apt-get install lm-sensors zcfanCode language: plaintext (plaintext)

(An alternative to zcfan is thinkfan, which requires slightly more configuration. The advantage of zcfan is that it functions out of the box.)

Packages

Ensure that the required firmwares are installed. On Debian- and Ubuntu-based distributions, install the following packages:

sudo apt install firmware-atheros firmware-realtek firmware-brcm80211Code language: plaintext (plaintext)

It is also recommended to install essential packages for hardware encoding and decoding, including intel-microcode for the latest processor updates, which improve system stability and performance:

sudo apt-get install intel-microcode i965-va-driverCode language: plaintext (plaintext)

TLP

TLP is a power management tool that optimizes battery life. Install and configure it as follows:

sudo apt install tlpCode language: plaintext (plaintext)

Create the configuration file /etc/tlp.d/00-base.conf:

DEVICES_TO_DISABLE_ON_BAT="bluetooth wwan"
DEVICES_TO_ENABLE_ON_STARTUP="wifi"
DEVICES_TO_DISABLE_ON_LAN_CONNECT="wifi wwan"

TLP_DEFAULT_MODE=BAT

CPU_SCALING_GOVERNOR_ON_AC=performance
CPU_SCALING_GOVERNOR_ON_BAT=schedutil

# PCIe Active State Power Management (ASPM):
PCIE_ASPM_ON_AC=performance
PCIE_ASPM_ON_BAT=default

# Runtime Power Management for PCIe bus devices: 
# on=disable, auto=enable.
# Default: on (AC), auto (BAT)
RUNTIME_PM_ON_AC=on
RUNTIME_PM_ON_BAT=auto

# Battery Care -- Charge thresholds
# Charging starts when the charge level is below the
# START_CHARGE_THRESH value when the charger is connected. It
# stops when the STOP_CHARGE_THRESH value is reached.
START_CHARGE_THRESH_BAT0=80
STOP_CHARGE_THRESH_BAT0=90

# Sound card power saving
SOUND_POWER_SAVE_ON_AC=0
SOUND_POWER_SAVE_ON_BAT=1

# Wi-Fi power saving
WIFI_PWR_ON_AC=off
WIFI_PWR_ON_BAT=on
Code language: Python (python)

Conclusion

The ThinkPad T420s, though older models, remain reliable machines for everyday tasks. With the right configuration, these laptops can be revitalized, making them well-suited for modern use.

jc-dotfiles – A collection of configuration files for UNIX/Linux systems

The jc-dotfiles repository houses James Cherti’s dotfiles and configuration scripts:

  • Shell Configuration (.bashrc, .profile, and .bash_profile): Optimized Bash shell settings for efficient command execution and interactive sessions.
  • Terminal Multiplexer (.tmux.conf): Configuration for Tmux, enhancing terminal session management and productivity.
  • Readline configuration (.inputrc): Inputrc configuration that also allows using Alt-h, Alt-j, Alt-k, and Alt-l as a way to move the cursor.
  • Other: .gitconfig (with support for difftastic), ranger, .fdignore, .wcalcrc, mpv, picom, feh, and various scripts and configuration files for managing system settings, aliases, and more.

Here are additional dotfiles and configuration files maintained by the same author:

  • jc-dotfiles @GitHub: A collection of UNIX/Linux configuration files. You can either install them directly or use them as inspiration your own dotfiles.
  • bash-stdops @GitHub: A collection of Bash helper shell scripts.
  • jc-gnome-settings: GNOME customizations that can be applied programmatically.
  • jc-firefox-settings @GitHub: Provides the user.js file, which holds settings to customize the Firefox web browser to enhance the user experience and security.
  • jc-gentoo-portage @GitHub: Provides configuration files for customizing Gentoo Linux Portage, including package management, USE flags, and system-wide settings.
  • jc-xfce-settings: GNOME customizations that can be applied programmatically.
  • watch-xfce-xfconf: A command-line tool that can be used to configure XFCE 4 programmatically using the xfconf-query commands displayed when XFCE 4 settings are modified.

Installation

Here’s how to install James Cherti’s dotfiles:

  1. Clone the Repository:

    git clone https://github.com/jamescherti/jc-dotfiles
  2. Navigate to the jc-dotfiles directory:

    cd jc-dotfiles
  3. Install:

    ./install.sh

Usage

.bashrc

  • Tmux/fzf auto complete: Pressing Ctrl-n calls a custom Bash autocomplete function that captures the current tmux scrollback buffer, extracts unique word-like tokens, and presents them via fzf for interactive fuzzy selection. The selected word is then inserted inline at the current cursor position using a readline binding.

  • The .bashrc file can be extended by adding configurations to ~/.bashrc.local.

  • The o alias calls a function that provides a cross-platform way to open files or URLs using the appropriate command for the system. This function opens files or URLs using the appropriate command (xdg-open on Linux, open on macOS, and start on Windows). If more than 7 arguments are passed, the user is prompted for confirmation before proceeding. Example usage:

    o file1.jpg file2.png file3.jpeg
  • Customizations in .bashrc to add to ~/.profile.local:

    # Use trash-rm as a safer alternative to rm by moving files to the trash instead
    # of deleting them permanently.
    #
    # JC_TRASH_CLI=1 replaces the standard 'rm' command with a wrapper function
    # that:
    # - Provides a detailed summary of all specified files and directories,
    #   including total size and file count.
    # - Prompts the user for confirmation before proceeding with the deletion.
    # - Moves files to the trash using 'trash-put' instead of permanently deleting
    #   them with 'rm'.
    # - Reports the current size of the trash in megabytes after each deletion.
    # - Optionally wraps 'trash-empty' with an interactive prompt before purging the
    #   trash.
    #
    # This setup is only activated for non-root users when 'trash-put' is available
    # and 'JC_TRASH_CLI' is set to a non-zero value.
    #
    JC_TRASH_CLI=1
    
    # Enable Emacs integration for vterm and EAT, configuring shell-side support for
    # features such as prompt tracking and message passing
    JC_EMACS_INTEGRATION=1  # Default: 0
    
    # Display the current Git branch in the shell prompt (PS1)
    JC_PS1_GIT_BRANCH=1  # Default: 0
    
    # Display the count of unread mails in the shell prompt (PS1)
    JC_PS1_MAILDIR=1  # Default: 0
    
    # Directory containing the mail (e.g., to "$HOME/Mail")
    JC_PS1_MAILDIR_PATH="$HOME/Mail"
    
    # Automatically restore the last working directory from the previous
    # interactive Bash session.
    JC_RESTORE_LAST_DIR=1  # Default: 0
    
    # Enable support for the fasd command-line utility, which provides fast access
    # to frequently used files and directories.
    JC_FASD=0  # Default: 0

License

Distributed under terms of the MIT license.

Copyright (C) 2004-2026 James Cherti.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Links

Related articles: