Author Archives: Lorenzo Bettini

About Lorenzo Bettini

Lorenzo Bettini is an Associate Professor in Computer Science at the Dipartimento di Statistica, Informatica, Applicazioni "Giuseppe Parenti", Università di Firenze, Italy. Previously, he was a researcher in Computer Science at Dipartimento di Informatica, Università di Torino, Italy. He has a Masters Degree summa cum laude in Computer Science (Università di Firenze) and a PhD in "Logics and Theoretical Computer Science" (Università di Siena). His research interests cover design, theory, and the implementation of statically typed programming languages and Domain Specific Languages. He is also the author of about 90 research papers published in international conferences and international journals.

Getting Started with LM Studio in Linux

I report my first experiences with LM Studio in (Arch) Linux.

LM Studio is a desktop application that allows you to discover, download, and run Large Language Models (LLMs) entirely on your own computers, offline.

This is the hardware and system I’m testing it (you see, no GPU with VRAM):

First, download the AppImage from the website https://lmstudio.ai/.

Run it, and follow the welcome steps:

It proposes to download the first model, which seems to fit my hardware (Gemma 4 e4b):

Finally, we get to the main interface:

Now, select the model (the only one currently downloaded)

And you can start to chat (in the following, I’m experimenting with creating a Maven project, but I’ll blog about such experiments in the future):

You can configure model parameters and possibly make such a configuration the default for the model:

For example, increase the context length:

Download other models using the UI for searching:

Note how LM Studio tells whether a model fits your hardware or is likely too large:

Alternatively, use the CLI “lms” to download a model:

During the downloading, the GUI also reports that:

Note that models are downloaded by default into “~/.lmstudio/models”:

OK, that’s all for this post!

Stay tuned for experiments with LM Studio and open source models.

Archinstall: dual boot with another Linux installation

In the past, I had several problems using archinstall for installing Arch Linux in a dual boot with another existing Linux installation. In fact, I gave up using archinstall and created my own script for installing Arch.

In particular, I experienced these two problems:

  • Archinstall failed installing Arch if the disk already contained a BTRFS partition
  • The “/boot/efi/” mount point could not be used: you had to use “/boot” (since all my other Linux installations use “/boot/efi”, which required a few tweaks that I was not willing to spend time on)

I tried once more with the July 2026 version, and both problems are finally gone (though, for the second one, there are a few things to know, as shown in the rest of this blog post).

Besides experimenting with Archinstall for dual boot with another Linux installation, I’ll also take the chance to quickly review a few parts of the installation procedure.

I experimented with the process on a KVM virtual machine, where I had already installed EndeavourOS, leaving some space for another installation.

Let’s keep an eye on the current state of “/boot/efi” and see later how Archinstall handles it.

And here’s the UEFI entries; the existing EndeavourOS installation is the “UEFI Misc Device”.

Let’s start the installation by booting the Arch Linux July 2026 ISO and running the Archinstall script:

The crucial part is disk partitioning, where we must select “Manual Partitioning”

Select the existing UEFI partition

And assign the “/boot/efi” mount point:

Then, select the empty partition, and create a new BTRFS partition:

Now, the sad part: I’m used to EndeavourOS or other Calamares-based installers that automatically create the default subvolumes when you create a BTRFS partition for the “/” mount point. Instead, in Archinstall, you have to deal with the subvolumes yourself:

Following the same process four times, I created the standard subvolumes (I skipped the ones for “/var/lib/portables” and “machines, which EndeavourOS creates, since I don’t care about them):

This is the final layout:

Now, select GRUB:

Here’s the crucial part: “Install to removable location”, enabled by default. If you press ENTER, you get more details:

The text explains a few things and tells you it is safe to leave it on.

However, if you do leave it on, then Archinstall will overwrite the NVRAM entry for existing Linux installations.

I tried both options in two different virtual machines; let’s see what happens:

Select NO

With this option, Arch will be the default boot option in the UEFI; however, existing entries can still be selected from the UEFI menu.

So, let’s select NO, and here’s what that entry reports:

Let’s conclude the installation and reboot.

Here’s the GRUB from Arch; let’s enter the UEFI settings

“GRUB” is Arch Linux, and “UEFI Misc Device”, if you look at the beginning of the blog post, is the existing EndeavourOS:

In fact, if we select it and press ENTER, we get to the EndeavourOS GRUB menu:

And here’s how “/boot/efi” has been modified:

Select YES

Let’s instead select YES (as I said, this is a parallel experiment on a different virtual machine, with the same existing EndeavourOS installation).

Of course, we have the Arch GRUB:

And the “/boot/efi/” still has the previously existing EndeaavourOS efi file.

However, if we enter the UEFI settings:

We no longer have an entry for EndeavourOS. This time, “UEFI Misc Device” is the Arch entry, not the EndeavourOS anymore.

However, we can manually re-add the EndeavourOS entry, because its EFI file is still there.

The procedure depends on the UEFI Firmware settings; in this case, we use the settings provided by “Virtual Machine Manager” Tiano Firmware settings:

We have to navigate to the EndeavourOS EFI file:

And now we have the EndeavourOS entry back (this time, with its real name):

Final thoughts

Things have improved, and Archinstall is probably usable for dual-booting now. I’ll try that on real hardware.

However, I still find it’s still far from being as useful as Calamares-based installers of EndeavourOS, CachyOS, or Garuda, to name a few.

Fixing LibreOffice Flatpak Opening Files from GNOME Dash

I use Fedora Silverblue with GNOME, and I have LibreOffice installed as a Flatpak.

Recently, I hit a strange problem: when searching for an office document from the GNOME Dash — for example, an .odt or .ods file in my Dropbox folder — and selecting it, LibreOffice appeared to start, but then failed with an error saying that a file like this did not exist:

The confusing part was that the file did exist at that path.

Even more confusing: if I removed the file from /run/user/1000/doc/..., the original file was removed too.

So what was going on?

The short version

The fix was:

This creates the following user override:

stored in:

After that, opening LibreOffice documents from the GNOME Dash worked again.

The setup

The system is Fedora Silverblue, so applications are mostly Flatpaks. LibreOffice is installed as:

The problematic documents live under Dropbox, for example:

Opening the same files with xdg-open worked fine:

Opening them directly with Flatpak also worked:

So LibreOffice itself was not broken.

The problem only happened when opening documents from the GNOME Dash search results.

The mysterious /run/user/1000/doc path

When GNOME opened the file from the Dash, LibreOffice complained about a path like this:

This is not a normal temporary directory.

It is part of the Flatpak / XDG document portal mechanism. The document portal exposes selected host files to sandboxed applications through a FUSE mount under:

The directory name that looks like a hash is a document portal ID.

In my case:

was the document ID.

The file under /run/user/1000/doc/... was not a copy. It was a portal-backed view of the original file. That explains why deleting it also deleted the original document.

Do not manually clean this directory with rm.

Inspecting the document portal entry

The useful command was:

It printed:

This confirmed several things:

  1. The /run/user/1000/doc/... path was a document portal path.
  2. The original file was the Dropbox file.
  3. GNOME was not launching the generic LibreOffice app ID.
  4. It was using the Writer-specific ID:

The important part is that GNOME Dash was creating a fresh portal export every time I tried to open a document.

So this was not stale recent-file history, nor was it a broken search index.

Things that did not fix it

I first tried granting LibreOffice access to my whole home directory:

That did not solve the GNOME Dash problem.

This makes sense in hindsight. The issue was not that LibreOffice could not access my Dropbox file directly. Direct opening already worked.

The issue was that GNOME Dash was giving LibreOffice this path:

not this one:

Granting access to home does not automatically make /run/user/1000/doc visible inside the Flatpak sandbox.

I also tried clearing recent files, resetting the GNOME file index, restarting portal services, and logging out and back in.

Still, every time I opened a new LibreOffice document from the Dash, a new directory appeared under:

That was the clue: GNOME was actively creating new document portal exports.

The actual fix

The working fix was to allow the LibreOffice Flatpak to see the document portal runtime directory:

Then kill any running LibreOffice instance:

After that, opening documents from the GNOME Dash worked.

The resulting override file is:

stored at:

This is much better than granting access to the whole home directory.

Why this works

GNOME Dash opens the document through the portal and passes LibreOffice a path like:

But the LibreOffice Flatpak sandbox did not have that runtime directory visible.

By granting:

we expose just the document portal mount inside the sandbox.

That is enough for LibreOffice to resolve the path that GNOME provides.

Keeping the fix in chezmoi

Since I manage my dotfiles with chezmoi, I added this file:

In chezmoi form, that is:

with this content:

That is all I need.

Useful commands

Show current LibreOffice Flatpak overrides:

Inspect a document portal entry:

Grant access to the document portal mount:

Remove the override again:

Kill LibreOffice after changing overrides:

Final result

The minimal override is:

This fixes opening LibreOffice documents from the GNOME Dash without granting LibreOffice access to the whole home directory.

The root cause appears to be this interaction:

LibreOffice was provided with a valid host-side portal path, but the sandbox could not find the corresponding runtime directory.

Granting xdg-run/doc bridges exactly that gap.

Bootstrapping chezmoi_modify_manager before chezmoi apply

If you use chezmoi_modify_manager together with chezmoi, there is one annoying bootstrap problem:

  • chezmoi_modify_manager is needed while chezmoi apply computes file contents
  • but the usual .chezmoiscripts/run_before_* hooks run too late to install it for the first apply

I wanted a solution that was:

  • automatic
  • shell-based
  • version-pinned
  • rerun only when the installer script changes

This post shows the setup I ended up with.

The problem

At first glance, it seems natural to put an installer script into .chezmoiscripts, for example, as a run_onchange_before_* script.

That helps with normal lifecycle management, but it does not solve the initial bootstrap.

Why? Because chezmoi needs to compute the target state before it runs those scripts, and modify_ files participate in that computation. If those files use chezmoi_modify_manager, then the binary must already exist before chezmoi apply reaches the script phase.

So the core issue is:

chezmoi_modify_manager must be installed before chezmoi apply starts doing the work that depends on it.

The approach

The solution is to split the problem into two:

  1. Put the installer in .chezmoiscripts as a run_onchange_before_* script, so the script is tracked by chezmoi and reruns when its contents change.
  2. Wrap chezmoi with a small shell function that manually runs that installer script before delegating to the real chezmoi apply.

That gives us the best of both worlds:

  • proper chezmoi-managed script in the source repo
  • successful first bootstrap
  • automatic reinstall when the installer changes, such as when bumping the pinned version

The installer script

I placed this file in:

~/.local/share/chezmoi/.chezmoiscripts/run_onchange_before_00-install-chezmoi-modify-manager.sh

The script is version-pinned, architecture-aware, and idempotent. It also keeps a tiny version stamp, so it can skip work when the correct version is already installed.

A few notes:

  • VERSION is deliberately hardcoded for simplicity
  • STAMP stores the installed version so the script can cheaply detect whether work is needed
  • --doctor provides a sanity check after installation

If you later want to upgrade, just change the VERSION string in the script. Because the script is a run_onchange_* script, chezmoi will detect the change.

Manual bootstrapping

After running “chezmoi init <URL>” and before running “chezmoi apply” for the first time, you must remember to manually execute this script

Alternatively, you can use the “wrapper” solution detailed next.

The wrapper

Now for the missing bootstrap step: we need to ensure the installer runs before chezmoi apply.

I used a shell function in my ~/.bashrc (or ~/.zshrc).

Then reload your shell:

With this in place, running:

does the following:

  1. finds the real chezmoi
  2. checks whether the installer script exists in the source directory
  3. runs the installer script first
  4. only then runs the real chezmoi apply

That is exactly what we need.

Why this works

This setup works because it respects the real dependency order.

The dependency is not:

“Run a script sometime before applying files.”

The dependency is:

“Ensure chezmoi_modify_manager exists before chezmoi starts evaluating anything that depends on it.”

A normal run_before_* script is still part of chezmoi’s internal apply pipeline, which is already too late for the very first bootstrap.

The shell wrapper moves the install step out of the pipeline and places it before chezmoi apply begins.

Why keep the installer in .chezmoiscripts at all?

Because it still gives you useful chezmoi behavior:

  • the installer lives in your dotfiles repo
  • it is versioned like everything else
  • changing the script content is part of your normal dotfiles workflow
  • the run_onchange_* naming still reflects the script’s intended lifecycle

In other words, the wrapper solves the bootstrap timing problem, while .chezmoiscripts keeps the implementation under chezmoi ‘s control.

Optional: ensure ~/.local/bin is on your PATH

If your shell does not already include ~/.local/bin, add it:

 

Happy dotfiles! 🙂

SWTBot Tests Failing on GitHub Actions macOS Because of “Welcome to Mac” (Setup Assistant Analytics)

My SWTBot UI tests recently started failing on GitHub Actions macOS runners.

The application under test is an Eclipse-based application, so at first I expected the failure to be caused by some SWTBot timing issue, focus issue, or maybe a regression in my own code.

However, the screenshot captured by the failed test showed something completely unrelated to my application:

That is not Eclipse. That is macOS Setup Assistant.

More specifically, it was the macOS Analytics screen asking whether to share analytics data with Apple and app developers. Since SWTBot drives the UI that currently has focus, this dialog was stealing focus from Eclipse and making the test interact with the wrong window.

The first workaround

My first attempt was to dismiss the Analytics dialog before starting the SWTBot tests:

This looked reasonable, but then the failure changed.

The new screenshot showed another Setup Assistant page:

This time it was the “Welcome to Mac” screen.

So the script was managing to click something, but Setup Assistant was still not completely gone. SWTBot was still losing focus before the actual Eclipse application could be tested.

The final workaround

The most robust fix was to avoid heredocs entirely and pass the AppleScript using repeated osascript -e arguments.

Here is the workflow step I now use:

This does three things:

  1. It checks whether Setup Assistant is running.
  2. It tries to press the visible Continue button.
  3. If Setup Assistant still refuses to disappear, it kills the process before the UI tests start.

After adding this step before launching Eclipse, the SWTBot tests started passing again.

Why this matters for SWTBot

SWTBot tests are particularly sensitive to focus problems.

They do not just test some isolated backend logic. They drive a real UI. If another native macOS window appears in front of the Eclipse workbench, the test can fail in confusing ways:

The actual problem may have nothing to do with SWTBot or Eclipse. The test may simply be interacting with the wrong application.

That is why screenshots from failed UI tests are invaluable. Without the screenshot, I would probably have spent a lot more time debugging Eclipse, SWTBot, or my own application.

Running Homebrew Commands with sudo on Linux and macOS

Sometimes a command installed with Homebrew needs to be executed as root. A good example is nethogs, which needs elevated privileges to inspect network traffic.

The problem is that this often fails:

even though this works:

The reason is that sudo usually runs with a different, restricted PATH. Your normal shell can find Homebrew commands, but sudo may not know where Homebrew installed them.

On Linux, Homebrew commands may live somewhere like:

On macOS, they are commonly under:

on Apple Silicon, or:

on Intel Macs.

The simple one-command solution

A quick workaround is to resolve the command path before calling sudo:

This works because which nethogs is evaluated by your normal shell, using your normal PATH, before sudo runs.

So instead of asking sudo to find nethogs, you give it the full path directly.

A shell function for one command

You can wrap that in a function:

This lets you run:

and still preserve arguments correctly.

A general brew-sudo helper

A better cross-platform approach is to define a small helper that asks Homebrew for its prefix, checks both bin and sbin, and then runs the matching executable with sudo.

Now you can run Homebrew-installed commands with root privileges like this:

This works across Linux and macOS because brew --prefix returns the correct Homebrew prefix for the current system.

Defining command-specific wrappers

You can then define convenient wrappers in terms of brew-sudo:

This gives you the best of both worlds:

while internally resolving to something like:

on Linux, or the corresponding Homebrew path on macOS.

Full version

Put this in a shell file sourced by both bash and zsh, or copy it into both ~/.bashrc and ~/.zshrc:

A note on sudo and Homebrew

This approach does NOT run brew itself with sudo. It only uses brew --prefix to locate Homebrew’s installation directory, then runs a specific Homebrew-installed executable with sudo.

That distinction matters: running brew install or brew upgrade with sudo is generally the wrong approach, but running a tool like nethogs with sudo is appropriate when the tool itself needs root privileges.

Alternative: adding Homebrew to /etc/sudoers

Another possible solution is to teach sudo about Homebrew’s executable directories.

On many systems, sudo uses a restricted path configured in /etc/sudoers, often through a setting called secure_path. You can inspect and edit it with:

You may see a line like this:

One could add Homebrew’s bin and sbin directories there. For example, on Linuxbrew:

Or on Apple Silicon macOS:

After that, this may work directly:

However, this approach has a security tradeoff.

It does not give users new sudo permissions by itself. A user still needs to be allowed to run commands with sudo in the first place. But it does make Homebrew-installed commands part of sudo’s command lookup path.

That can be undesirable because Homebrew prefixes are often writable by the normal user, or by users in an admin group. In general, root’s PATH should avoid directories that non-root users can modify. Otherwise, it becomes easier to run a user-controlled executable as root accidentally.

For that reason, I prefer the wrapper approach shown above.

Bootstrapping chezmoi from HTTPS to SSH After First Apply

When I bootstrap a new machine with chezmoi, I usually start by cloning my dotfiles repo from GitHub over HTTPS:

That works well for the very first setup, because my SSH keys are not installed yet.

After the first chezmoi apply, though, my SSH keys are finally in place, and I want the dotfiles repository itself to switch from HTTPS to SSH automatically.

This sounds simple, but there is a small gotcha.

The goal

I wanted chezmoi apply to do this at the end of the first run:

  • detect that the repo remote is using HTTPS
  • convert it to the SSH form
  • update origin automatically

For example:

  • from https://github.com/username/dotfiles.git
  • to git@github.com:username/dotfiles.git

My first attempt

My first version used a run_once_after_... script that called chezmoi source-path to find the source repo:

But when I ran chezmoi apply, I got this:

Why this fails

The problem is that the script is being run by chezmoi apply.

So if the script itself runs another chezmoi command, the second process tries to acquire the same persistent state lock that the original chezmoi apply already holds.

In other words:

  • chezmoi apply starts
  • it runs your script
  • your script runs chezmoi source-path
  • the nested chezmoi process waits on the lock
  • eventually it times out

The fix

The solution is to avoid calling chezmoi inside the script.

Instead, make the script a template and use {{ .chezmoi.sourceDir }} to inject the source directory path when chezmoi renders the script.

Here is the working version.

Working script

Create this file:

Contents:

Why this works

There are two important details here.

1. The script is a template

The .tmpl suffix tells chezmoi to render the file before executing it.

That means this line:

gets replaced with the actual source directory path ahead of time.

No extra chezmoi process is needed.

2. The script runs after “apply”, and only once

Because the file name starts with run_once_after_, chezmoi runs it after the apply phase, and only once per machine unless the script changes.

That makes it a good fit for first-boot setup tasks like this one.

A note about the working directory

One subtle point: chezmoi scripts do not run in the source repo by default.

So even though we know the source directory, we still need to explicitly run:

before calling git remote get-url or git remote set-url.

Resetting the script state

If you need to test the script again after it has already run successfully, you can clear chezmoi’s script state:

That lets run_once_ scripts execute again.

Final thoughts

This ended up being a neat example of a general chezmoi rule:

If a script is already running inside chezmoi apply, avoid launching chezmoi again from that script.

When you need chezmoi-specific information in a script, templating is often the better solution.

For my bootstrap flow, this gives me the best of both worlds:

  1. clone over HTTPS when no SSH key exists yet
  2. install SSH keys during the first apply
  3. automatically switch the dotfiles repo to SSH at the end

Exactly what I wanted.


If you use a similar bootstrap flow, hopefully, this saves you from the persistent state lock timeout.

Fedora Silverblue: upgrading

I’m continuing my evaluation of the Fedora Silverblue Linux distribution, which I started some time ago.

Let’s upgrade the system!

As mentioned in the official documentation:

On Atomic Desktops, you can keep using your computer while applying OS updates as they are downloaded and installed in the background. Once an update has been installed, you can reboot your computer to start using the new version. You will not have to wait for the update to be installed either during shutdown or boot up.

On Fedora Silverblue and Fedora Kinoite, OS updates are downloaded automatically and you will be notified when updates are ready to be applied via a reboot. This behavior can be changed in the settings.

On a fresh installation, these are the details of the installed system:

If we open the Gnome Software, we can see that updates are already detected:

Actually, the system is already downloading the updates (and applying them on a new image); after a while, you can simply restart:

After the reboot, the updated image will take effect:

In particular, just rebooting will lead you to a possibly already downloaded and updated image.

Automatic update behavior can be seen in the Gnome Software preferences:

From the command line, you can check available updated images:

Note the leading “dot” telling you what image you’re currently on.

After reboot, here’s the updated information:

You can also use the GRUB menu to possibly boot into the previous image:

You can then verify that you booted into the previous image:

The “status” command also shows when the new image is being downloaded; for example, after a fresh installation, a new upgrade will be automatically checked and downloaded:

You can also update using the command line. To check for available updates without downloading them, run:

For example, let’s say that I don’t have the latest update (see the status), I check for possible new updates:

I can perform the upgrade with:

And reboot.

With “–preview”, you can also see what’s going to be upgraded, for example:

Stay tuned for more blog posts about Fedora Silverblue!

Running Waybar with a Custom Configuration Directory in Sway

When setting up Sway as your Wayland compositor, you might want to organize your Waybar configuration in a non-standard location — for example, keeping a dedicated ~/.config/waybar/sway/ directory to separate your Sway-specific bar configuration from others.

This seemingly simple task comes with a couple of subtle pitfalls worth knowing about.

The ~ Expansion Problem

The most natural approach would be to launch Waybar directly from the bar block in your Sway config:

Unfortunately, this does not work. The swaybar_command directive does not perform shell expansions, so ~ is passed literally to the command instead of being expanded to your home directory. Waybar will fail to find the configuration file.

A workaround is to launch Waybar via exec instead:

However, this approach has its own drawback: Waybar launched via exec is no longer managed by Sway’s bar subsystem. This means that Sway bar commands — such as swaymsg bar hidden_state toggle to show/hide the bar — will not work.

The Solution: A Wrapper Script

The cleanest solution is to write a small wrapper script that handles the expansion and launches Waybar with the desired configuration:

Make it executable:

Then reference it in your Sway config:

Now Waybar is properly managed by Sway’s bar subsystem, and the script handles path expansion.

The PATH Problem

There is one more subtle issue: the script lives in ~/.local/bin/, which may not be in PATH when Sway starts. Sway is typically launched from a display manager or TTY, where the environment is minimal and does not source your shell’s configuration files (e.g., ~/.bashrc or ~/.zshrc).

To ensure ~/.local/bin is in PATH for Sway and all processes it spawns, add it in your login shell profile:

Your display manager or TTY login will source this file, making the script discoverable when Sway starts.

A first look at Fedora Silverblue 43

I’m going to test the Fedora 43 Linux distribution in its “immutable” “atomic” shape, Silverblue.

Fedora Silverblue is Fedora’s atomic, GNOME-based desktop built around an image-based, mostly read-only system rather than a traditional mutable Linux install.

  • As an atomic desktop: the base OS is image-based, updates are applied as a new deployment, and you switch to it on reboot. That gives you an easy rollback if an update goes wrong.
  • It is designed to separate apps from the host OS: GUI apps are primarily installed as Flatpaks, command-line/dev tooling is commonly run in Toolbx containers, and traditional RPMs can still be added through the rpm-ostree layering when needed.
  • The main upside is stability and reproducibility: Fedora says Atomic Desktops are intended to be more stable, easier to test, and well-suited to containerized apps and development.

Thus, it is rather different from other standard “mutable” Linux distros, especially when it comes to installing software.

I’m starting a series of blog posts on Fedora Silverblue.

At the moment, I’m NOT considering using it as my daily driver (which, for now, is Arch, specifically EndeavourOS).

The installation process

The installation procedure is the typical Fedora one:

It detected my language (though I then switched to English):

Then, I switched to the Italian keyboard layout:

I checked the time, and it was already correct; I also checked the Internet time synchronization:

Time to deal with disk partitioning (“Installation Destination”).

Since I’m testing this on a virtual machine, I’ll stick with the automatic partitioning.

And we can now start with the installation:

The “Writing objects” text stays there for a long time.

Then, the rest of the installation steps are performed, including the installation of apps:

Then, the progress bar basically goes directly to the end. The installation completed in a few minutes:

First boot

During the first boot, you configure a few things (most of them, taken from the installation process, but to be confirmed):

I’ll skip the GNOME tour.

The installed system

Concerning installed applications, it’s rather minimal:

You have a terminal (not the Gnome terminal, but “ptyxis) and Firefox:

Let’s see the layout of the partitions:

So the main partitions are BTRFS.

Note the symlinks (especially for home):

Here’s the Gnome software center:

Of course, since Silverblue is immutable (you can’t modify the root system), applications are installable through flatpaks from the software center:

Concerning the installed apps:

They are also flatpaks.

However, concerning the “System Apps”:

I seem to understand they are “layered” on the base image:

There are already some updates:

We’ll deal with upgrades in a future blog post.

That’s all for the moment, but I’ll keep exploring and experimenting with this distro.

Neovim and LaTeX with LazyVim, part 3: custom commands

This is a follow-up to the first post on Neovim and LaTeX and the second one.

If you write LaTeX in Neovim using the LazyVim distribution, you already get a fantastic editing experience thanks to VimTeX and texlab. But there’s one thing that often trips people up: how do you quickly wrap selected text in \emph{…}, \textbf{…}, or other LaTeX commands?

The answer is mini.surround — and with a small buffer-local configuration, it becomes a LaTeX wrapping powerhouse.


What mini.surround Gives You (Out of the Box)

LazyVim ships with the mini.surround extra, which you can enable in your lazy.lua (or wherever you manage extras):

Once enabled, you get operations like:

Keymap Action
gsa Add surrounding
gsd Delete surrounding
gsr Replace surrounding
gsf Find surrounding

For example, visual-select a word and press gsa" to wrap it in double quotes, or gsa) for parentheses. Great — but what about LaTeX-specific commands?


The Problem with LaTeX Commands

VimTeX gives you text objects, motions, completion, and much more — but it doesn’t ship a built-in “surround this selection with \emph{…}” feature. That’s where mini.surround‘s custom surroundings come in.

We can teach mini.surround to understand LaTeX commands by defining custom surroundings in a filetype-specific Lua file.


Setting Up Custom LaTeX Surroundings

Create (or edit) the file ~/.config/nvim/after/ftplugin/tex.lua. This file is automatically sourced by Neovim whenever you open a .tex file.

Why ftplugin? Files in this directory are loaded after all plugin configuration, and only for the matching filetype. This means our settings are buffer-local and don’t interfere with any other filetype.


How It Works

The cmd_surround helper function does two things:

  1. input: A Lua pattern that matches \cmd{...} so that gsd and gsr know what to find and remove.
  2. output: The left (\cmd{) and right (}) strings used when adding a surrounding.

We then assign short, memorable single-character IDs to each command.


Using the Surroundings

Here’s what you can do in any .tex buffer:

Adding a Surrounding

Visual-select some text, then:

Keymap Result
gsae \emph{selected text}
gsab \textbf{selected text}
gsai \textit{selected text}
gsat \texttt{selected text}
gsau \underline{selected text}
gsas \textsc{selected text}
gsaq (add opening and closing LaTeX quotations, i.e., two backticks and ”)

You can also use gsa in normal mode with a motion: gsaeiw wraps the current word in \emph{…}.

Deleting a Surrounding

Place your cursor inside a command and press:

Keymap Action
gsde Remove \emph{…}, leaving the inner text
gsdb Remove \textbf{…}
gsdi Remove \textit{…}

Replacing a Surrounding

Want to change \emph{…} to \textbf{…}? No problem:

That’s gsr (replace surrounding), then e (find \emph{…}), then b (replace with \textbf{…}).


A Practical Example

Suppose you have this text:

You visual-select “very important” and press gsab. You get:

Now you decide that emphasis is better. With the cursor inside the \textbf{…}, press gsrbe:

And if you want to remove the markup entirely: gsde:


Extending with Your Own Commands

The pattern is easy to extend. Just add more entries to cfg.custom_surroundings:

Pick single characters that are easy to remember and don’t conflict with built-in mini.surround ids (avoid (, ), [, ], {, }, ', ", `).


A Note on Limitations

The input patterns use Lua’s pattern matching, which cannot handle nested braces. For example, \emph{foo {bar} baz} may not be matched correctly by gsd or gsr. For the vast majority of LaTeX writing, this is not an issue — but it’s worth keeping in mind if you work heavily with nested commands.


Happy LaTeX writing! 🎓

Run mako in Sway/Hyprland, but not in KDE (Wayland)

If you use a Wayland compositor like Sway or Hyprland, mako is a great lightweight notification daemon. Many distros make it “start automatically” via a systemd user service, so you don’t need to add exec mako to your compositor config.

I’m experimenting with a setup with KDE, Sway, and Hyprland installed on the same machine and used by the same user, and there’s a catch: KDE on Wayland is also a Wayland session, so the same auto-start logic can kick in there too — and you end up with mako running in Plasma when you don’t want it.

This post shows a clean fix: keep mako auto-starting in Sway/Hyprland, but skip it in KDE, using a systemd drop-in override that lives nicely in your dotfiles (e.g., with chezmoi).


Why mako starts in KDE

On many systems, the provided user unit checks only whether you’re in a Wayland session:

KDE Wayland sets WAYLAND_DISPLAY too, so mako passes the check and runs.


The fix: restrict the systemd unit to Sway/Hyprland

Create a systemd user drop-in:

This opens an editor and writes a file under:

  • ~/.config/systemd/user/mako.service.d/override.conf

Put this in that file:

Apply it:

Now mako should start in Sway/Hyprland, and be skipped in KDE.


Verify what systemd is using

To see the merged unit (vendor file + your override):

To confirm whether it’s running and why:

If you log into KDE and it’s skipped, you’ll typically see a reason related to the ExecCondition.


Making it portable with chezmoi

Since systemctl --user edit produces a file in your home directory, it’s perfect for dotfiles.

With chezmoi, just track:

  • ~/.config/systemd/user/mako.service.d/override.conf

On another machine, once mako is installed, that drop-in will be picked up automatically. The only thing to remember is that systemd reads unit changes after a reload (or next login), so if you want it to take effect immediately after chezmoi apply, run:

(try-restart is nice because it won’t error if mako isn’t running.)


Bonus note: D-Bus activation

Depending on your distro and setup, mako can also be started via D-Bus when a request for notifications is made. If you find mako still appears in KDE even when the unit is skipped, check whether it’s being D-Bus activated — and then either:
– rely on a KDE-native notification daemon in Plasma, or
– mask the D-Bus service for org.freedesktop.Notifications for KDE sessions only.

(Usually, the systemd drop-in above is enough.)

Using KWallet in Sway for Chrome

If you use KDE and Sway (or Hyprland) on the same machine with the same user (something I’m experimenting with), when you launch Chrome and log in with your user in KDE, and then switch to Sway, you’ll see that your account is marked as “Paused”: you have to log in again. The same holds the other way round.

That’s because in KDE, Chrome stores the credentials in KWallet, while Sway does not.

To fix this annoying problem, you have to ensure to run Chrome in Sway with the option “–password-store=kwallet6”.

To do that, you can either manually launch Chrome with that option or create the file “~/.local/share/applications/google-chrome.desktop” starting from the default file (in Arch it’s “/usr/share/applications/google-chrome.desktop”) and ensure the occurrences of the “Exec” line have that option, i.e.,

The desktop file in your home folder will have precedence over the default one.

However, while KDE automatically unlocks the KWallet when you log in, Sway does not. The first time you launch Chrome from a Sway session, you’re asked to open the wallet with your login password:

To let KWallet use your login credentials automatically (as KDE does, thanks to “/etc/xdg/autostart/pam_kwallet_init.desktop”), you need to tell Sway to start the corresponding PAM module.

The ArchWiki recommendation for “a window manager” setup is simply: run pam_kwallet_init from your WM/compositor startup.

Add this to ~/.config/sway/config (early in startup, before apps that need secrets):

That’s usually enough to avoid the post-login password prompt, provided PAM is already set up correctly (see the next section).

Important: pam_kwallet_init only works if PAM captured your password

pam_kwallet_init does not magically know your login password. It relies on the PAM module (pam_kwallet5.so) having captured it during login and made it available for the session handoff.

So:

  • If you start Sway via a display manager/session that already has pam_kwallet5.so in its PAM stack, then running /usr/lib/pam_kwallet_init In Sway, the wallet should unlock automatically.

  • If you start Sway via a path that doesn’t run the kwallet PAM module (common when starting from TTY, or via some greeters, depending on config), then pam_kwallet_init won’t have credentials to use, and you’ll still be prompted.

Installing Linux on a Dell Pro Max Tower T2

I’ll show how I installed Linux (EndeavourOS, i.e., Arch) on a Dell Pro Max Tower T2.

This is quite a powerful computer! Here are a few screenshots taken from Windows 11:

Before installing Linux on this computer, I had to change the SSD SATA mode from RAID to AHCI, as documented in a previous blog post. Otherwise, Linux will not detect any SSD.

Prepare the disk with “Disk Management”.

Current situation:

I will not wipe the whole disk because I want to use Windows as well. I won’t touch the other recovery and health partitions either.

Right-click on “C:” and choose “Shrink Volume…”. About 200Gb should be enough for Windows on this computer. Unfortunately, the UI of this dialog is not the best one: you have to compute how much space to remove from the current partition and check whether the “Total size after shrink” is what you want.

I’m also deleting the “D:” volume (I’ll use it for additional partitions both on Linux and maybe on Windows).

Here’s the final result:

Let’s reboot the computer and turn off secure boot. Press F2 when the computer is turning on to enter the BIOS.

Very nice looking:

NOTE: You can use the mouse to navigate the BIOS. In my case, the computer is connected to a KVM switch for keyboard and mouse. When in the BIOS, the mouse just moves vertically. I had to plug the mouse directly into the USB port of the computer to use it inside the BIOS.

Select “Boot Configuration” where you see the “Boot Sequence” (that’s useful in the future to change the boot order or delete old entries). Scroll down til you get to secure boot and disable it:

Let’s apply changes and exit.

I downloaded the EndeavourOS ISO Mercury Neo 2025.03.19, put it into a Ventoy USB stick.

It looks like this PC can boot from USB only from the first port from the bottom (at least in the front: I haven’t tried the ports on the back):

When the computer starts, press F12 for the temporary boot menu. Select the USB stick. Then you get the Ventoy menu where you select the EndeavourOS distribution. I’ll use grub2 mode.

The installation is the typical one for EndeavourOS, which I have already blogged about.

After several weeks, I can confirm that Linux works like a charm on this powerful computer! 🙂

Install Endeavouros with Disk Encryption

A small blog post on how to install EndeavourOS with LUKS disk encryption.

The installation starts and proceeds as usual (see, e.g., my older post). I’m using KDE for this installation.

When you get to disk partitioning, choose manual:

If you start from a fresh disk, create a new partition table and choose GPT:

If you start from a fresh disk, first create the partition for EFI:

In the rest of free space, I personally prefer to have a swap partition:

But I prefer not to encrypt that (to avoid being asked for the decryption password twice; at least, that’s what I guess… I’ll experiment that in the future):

Then, the partition for the actual system (in this case, I’m not using the whole disk); here, you specify the filesystem (I prefer BTRFS) and check “Encrypt”; you’ll be asked for the encryption password (of course, choose a strong one and ensure you remember that password):

Here’s the final layout:

Now, proceed as usual.

When you reboot, before getting to GRUB, you’ll be asked for the encryption password:

Once inserted, wait for the system to verify that:

And then, you finally get to GRUB as usual.

In fact, I haven’t created a separate partition for “/boot” (which would be a bad idea if you want to use BTRFS snapshots); thus, the grub configuration is in the encrypted file system, and when EFI boots, it needs the encryption password right away.

When you log in, you should see the directory layout with LUKS encryption:

Enjoy your encrypted system! 🙂

Neovim and LaTeX with LazyVim, part 2: customizations

This is a follow-up to the first post on Neovim and LaTeX.

I love LaTeX, but I don’t love LaTeX noise. If you use LazyVim’s lang.tex extra, you’ve probably seen a familiar friend pop up in your editor diagnostics: Underfull \hbox. It’s usually harmless, but it’s distracting—especially when you’re trying to focus on content. In this post, I’ll show two clean ways to silence those warnings: one for VimTeX’s quickfix and one for TexLab’s LSP diagnostics, without turning off the good stuff.

In LazyVim’s Tex extra, those Underfull \hbox … messages are coming from VimTeX’s Quickfix parsing (it’s enabled by default in the extra). VimTeX lets you hide specific warnings by regex-matching them with g:vimtex_quickfix_ignore_filters.

Create a custom plugin specification, e.g., “~/.config/nvim/lua/plugins/extend-vimtex.lua”, with these contents:

Restart Neovim, and the quickfix list will no longer contain those warnings.

However, you’re still seeing “Underfull \hbox” as LSP diagnostics (not quickfix), that’s coming from TexLab (separate configuration):

You need to create another custom plugin specification for customizing the TexLab LSP, e.g., “~/.config/nvim/lua/plugins/extend-lspconfig.lua”:

Restart, and also the editor’s inline warnings of that space will be gone.

If you don’t like that deep specification, in LazyVim, you don’t have to restate the whole opts = { servers = { texlab = { … }}} block. You can patch the existing LSP config in a tiny opts = function(_, opts) ... end snippet (LazyVim shows this pattern for extending defaults, and also documents the opts.setup hook for server setup).

Here’s an alternative configuration:

Though it’s a bit longer.

The final result of this series of tutorials can be found here: https://github.com/LorenzoBettini/lazyvim-tex. Branches will denote the state of the repository for a specific blog post section.

Stay tuned for more blog posts on LaTeX and Neovim! 🙂

Stop VS Code’s Java LSP from Rewriting Your Eclipse .classpath with m2e-apt Entries

How to prevent JDT LS (via m2e) from adding generated-sources APT folders and org.eclipse.jdt.apt prefs to an Eclipse+Maven project in VS Code.

If you open a Maven Java project in Visual Studio Code that also contains Eclipse project metadata (.project, .classpath, .settings/…), you might notice that VS Code’s Java tooling (JDT Language Server) “helpfully” edits your Eclipse files.

In particular, it may keep re-inserting entries like these into your .classpath:

…and it may also create this file:

.settings/org.eclipse.jdt.apt.core.prefs

VS Code Java support is powered by Eclipse JDT Language Server (JDT LS). When it detects Eclipse metadata (.project / .classpath), it will often use Eclipse-style project configuration and keep it synchronized.

For Maven projects, JDT LS relies on m2e (the “Maven integration for Eclipse”), and in many setups m2e-apt is present as well. m2e-apt is the component that manages annotation processing (APT) integration and, as part of that, it adds the standard “generated sources” folders into .classpath.

I find that very annoying!

If your project doesn’t use annotation processing and you don’t want these Eclipse files constantly modified, and you remove the entries, Visual Studio Code will re-add them when you open the projects in Visual Studio Code. If you open the projects from Eclipse and you “update” the Maven projects, Eclipse will remove the entries… and so on and so forth!

Here’s how to fix things for good.

The fix: disable m2e-apt at the project level

m2e-apt supports overriding its activation per-project using a Maven property:

Add this to your pom.xml

Put it under the regular Maven &lt;properties&gt; section:

That’s it. After this, m2e-apt will stop treating your project as something it should manage, and VS Code/JDT LS will no longer keep reintroducing those APT-related .classpath entries.

Note: The documentation mentions a “settings section” in the POM. There is no settings element in pom.xml; Maven “settings” live in ~/.m2/settings.xml. In the POM, this is implemented via a property, so properties (or a profile’s properties) is the right place.

Refresh VS Code so it stops regenerating the files

After editing the POM, VS Code may still have cached the project configuration. Do this once:

  1. Open the Command Palette
  2. Run: Java: Clean Java Language Server Workspace
  3. Let the Java server restart and re-import the project

Then you can delete the unwanted entries/files one last time:

  • Remove the APT-related classpathentry … m2e-apt … blocks from .classpath
  • Delete .settings/org.eclipse.jdt.apt.core.prefs if you don’t want it around

They should not come back.

If you only want to disable it in certain environments, you can place the property in a Maven profile or in the pom file of a single project.

Happy (quiet) classpaths! 😉

LaTeX listings: Eclipse colors

This is the style I use to highlight my Java code in LaTeX documents with the Listings package, with Eclipse colors:

And this is an example of a document, where I show the same listing both with black and white colors an Eclipse colors:

Here’s the result:

Browse and run your Sway keybindings with Rofi

Remembering every Sway shortcut is tough. I wrote a small script that parses your Sway config, displays all bindsym shortcuts in a clean, searchable list via Rofi, and executes the command associated with whichever one you select.

It’s fast, keyboard-friendly, and great for discovery: “What did I bind to Mod + Shift + P again?” Now you can search, see, and execute it.

What the script does

  • Reads your Sway config from $XDG_CONFIG_HOME/sway/config (or ~/.config/sway/config)
  • Finds all bindsym … lines
  • Formats each entry nicely, e.g.

Mod + Return → exec alacritty

  • Shows the list in a wide Rofi dmenu
  • When you select an entry, it executes the associated command through swaymsg

Dependencies

  • sway (for swaymsg)
  • rofi
  • awk, sed, grep (standard on most distros)
  • notify-send (optional – shows an error if the config isn’t found)

The script

Save this as ~/.local/bin/rofi-sway-keybindings.sh and make it executable.

How it works

The core of the script is a small text-processing pipeline that reads the config and renders a nice two-column list for Rofi:

  1. grep -E '^\s*bindsym' finds all bindsym lines (ignoring leading whitespace)
  2. grep -v '^[[:space:]]*#' ignores full-line comments
  3. sed 's/^\s*bindsym\s*//' strips the leading bindsym
  4. awk splits the line into (and does some cleanup):

keys: the first token (e.g. $mod+Return)
cmd: the rest of the line (e.g. exec alacritty)

It also strips trailing inline comments (after #) and skips bindsym flags like --release or --locked before reading the key. Finally, it prettifies modifiers and prints a fixed-width column so the arrows line up.

Rofi presents that list with -dmenu. When you pick one, the script extracts the command part (after ) and sends it to swaymsg. That means anything you can put after bindsym (like exec …, workspace …, kill, etc.) will run on demand.

Usage

  • Run the script from a terminal: rofi-sway-keybindings.sh
  • Or bind it to a key in your Sway config, e.g.:

Tip: the window is set to 60% width for readability; tweak it via -theme-str if you prefer.

Nice touches in the UI

  • Replaces $mod with Mod
  • Shows Shift as and Control as Ctrl
  • Adds spacing around + so chords read clearly: Mod + ⇧ + q
  • Aligns the left column to 35 characters for a tidy two-column look

Why this is handy

  • Onboarding: perfect for new setups or when you come back after a while
  • Discovery: search by key or by command to find what you already have
  • Launcher: use it as a programmable “cheat sheet” that also runs things

Here are some screenshots (including filtering):

Modern Java in LaTeX listings (Java 17)

If you use the LaTeX listings package to typeset Java, you’ve probably noticed that modern Java has moved faster than the package itself. Records, var, and text blocks may not highlight correctly out of the box. The good news: the listings package is extensible so that you can teach it “modern Java” with a tiny language definition.

The minimal language extension for Java 17

Here’s a drop‑in snippet that builds on the stock Java lexer to support key Java 17 features:

What each line does:

  • language = Java: inherit all of the listings’ built‑in Java rules.
  • morekeywords = {var,record}: colorize var and record as keywords (var is contextual, but highlighting it improves readability in code listings).
  • deletekeywords = {label}: avoid mistakenly highlighting labeled statements like label: for (…) { … }. label is not a Java keyword; removing it prevents false positives.
  • morestring=[b]”””: treat triple quotes as a balanced string delimiter so Java text blocks highlight as a single string.

Using it in your document

Activate the language globally:

…or per listing:

If you already have a custom style (e.g., mystyle) with colors and fonts, combine them:

Minimal working example

This is a compact MWE you can compile with pdflatex, xelatex, or lualatex. Adjust the style to your taste:

Here’s the result, where you can see the differences (note in the standard behavior the wrong highlighting of the double-quoted string in the text-block):

Happy highlighting! 🙂