GNOME Shell Extensions — Automating tests on a VM with SSH
Developing extensions for the GNOME Shell can be challenging when it comes to debugging new applications. During my development process, I faced some difficulties related to the necessary tests for the development of a small project.
In this article, I will present one of the solutions I found to make the development safe and agile.
The anatomy of a GNOME Shell Extension
GNOME Shell extensions work by enhancing and modifying the interface and functionalities of a GNOME environment. They can usually be obtained from both the official store and the repositories themselves, distributed in .zip format.
The anatomy of an extension includes at least the metadata files, in addition to the extension itself:
- metadata.json: Contains information about the extension;
- extension.js: The extension itself.
The extension directory must also follow some rules to work, and must be named with the same UUID present in metadata.json. There are other elements, such as configuration and translation files for the tool, used as needed by the application. The complete anatomy can be consulted in the official documentation.
After obtaining the application, the next step is to install it on the machine, extracting all the content within the standard directory, in “~/.local/share/gnome-shell/extensions”.
This process is done automatically by GNOME itself when we install an extension directly from the site with the help of GNOME Extensions, but in this case we will perform it manually.
Creating a Virtual Machine
If your machine already has a GNOME environment, as in Ubuntu distributions, you can consider testing the development directly on the desktop, but this practice ends up leading to instabilities and conflicts with other extensions. Instead, it will be safer to test the project on a virtual machine, in a controlled and easy-to-maintain environment.
For this case, it will be necessary to use the SSH protocol, with a virtual bridge-type interface so that the VM can be accessed by the host through the network. There are some solutions that meet these requirements, such as VirtualBox, but in this example we will use Virt-Manager, which brings more comprehensive customization.
The process of creating a VM in Virt-Manager is similar to what is found in other market options. To start the wizard, we should start at File>New Virtual Machine.
Virt-Manager offers support for downloading ISO files directly from the server, but in this case we will use a local image.
With the system selected, now we need to define how much RAM and CPU cores will be reserved for the virtual machine, as well as the disk space it will use.
At the end, we can review the process and name our VM.
From this point on, we will have the standard installation of a Linux distribution, according to your choice. In this case, we are using an Ubuntu 24.04 ISO, which has a new installer. The complete process of this version can be seen in this article, but it does not differ much from the previous versions.
Now we can normally initialize the virtual machine. If the installation is initialized again, check if the file is still present in “SATA CDROM” and remove it.
Configuring the Network Interface
By default, the virtual machines created in Virt-Manager use a virtual NAT-type interface, useful for internet access from the guest machine, but it does not allow the VM to be directly discovered by other devices on the network, since its private IP is translated to the IP on the host machine.
This would not be a problem if we were just transferring files between machines, as the virtualizers offer support for shared folders, but as the intention is also to execute commands on the VM from the host machine, a more complete approach with a bridge interface will be necessary.
A virtual interface in bridge mode allows the user to expose the virtual machine directly on the local network to all devices present on the LAN, including the host itself. The guest machine gets its own IP on the same internal subnet, behaving like any other physical device, allowing full remote access via SSH.
To configure a virtual network interface of the bridge type in Virt-Manager, it is only necessary to change the “Network source” to “Bridge device”, adding the name of the virtual interface available on the desktop, which in this case is “virbr0”.
The virbr0 is a virtual bridge-type network interface that comes pre-configured among the network interfaces of a Linux distribution with all the necessary elements for this project. In my case, with Ubuntu 23.10 as the host, no other operations were necessary to use it. You can check if it is present in your system from the nmcli via the command line.
nmcli --showThere is also a third visual option, through the network manager available on your machine, which here is the Network Connections.
Creating your own Bridge Interface
If the virbr0 interface is not present on your machine or you prefer to customize a new interface to maintain the organization of the project, it is still possible to configure an exclusive bridge for your machine.
This configuration can be done with the “nmcli” utility, used earlier to view the available network interfaces, or through the “ip” tool. If you choose to use any of these tools, you can check the step-by-step in this documentation, but for this project we will use a third alternative: Netplan.
Netplan is a resource that allows you to create and configure networks from a YAML file, which has been officially present in Ubuntu since version 18.04. The main advantage of this tool is the ease of backup, as we can save the configuration file to create the same network on any other machine that supports it.
The configuration files can be saved in the “~/etc/netplan/” directory and have this initial format:
network:
version: 2
renderer: NetworkManagerIt can also contain other information, such as the physical network interface being used, gateway information, DHCP and more. For this project, in building a standard bridge interface for communication only between the host and the guest, the following configuration will be used:
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: no
bridges:
extbr0:
interfaces: [enp3s0]
addresses: [<ip_address>/24]
gateway4: <ip_gateway:
nameservers:
search: [extbr0.com]
addresses: [8.8.8.8, 8.8.4.4]
parameters:
stp: false
forward-delay: 0
dhcp4: noIn this case, we have:
- ethernets>enp3s0: My physical network interface. You will need to change it to the name of your interface, which can be checked with “ip addr” from the command line;
- bridges>extbr0: “extbr0” is the name given to the new bridge interface. Note that in “interfaces” the same physical interface named earlier is listed;
- extbr0>nameservers>search: Interface domain name. Useful when working with hostnames.
Netplan can offer much greater support for configurations. If you need something more complex, consult the documentation.
Get Manoel Freitas’s stories in your inbox
Join Medium for free to get updates from this writer.
With the new interface created and included in the Netplan directory, we must enable the new network for everything to take effect.
sudo netplan applyAfter that, we must update the device name in the virtual interface and restart the guest machine system for everything to be applied correctly.
Note that in the previous Netplan configuration, we did not activate DHCP, leading to the need to manually assign a static IP to the machine. In this project we are dealing with a single machine, on an exclusive virtual interface, so there is no need to use DHCP. However, if your project uses multiple VMs, consider using it.
At the end, we can use ping on both machines to verify that everything is working correctly.
ping <ip_address>Remember that bridge mode exposes the VM to all devices on the network and this can bring security risks, and it is also recommended to activate protective measures, such as a firewall, on the VM and on your host machine.
SSH Protocol
With the VM created, we can start configuring the SSH protocol, which allows communication between the two machines. SSH is a protocol already widely used for communication between machines over the network with encryption. With it, it is possible to share files, manage data and execute commands on the guest machine from any other device.
We must have the openssh utility installed on the virtual machine to be able to access it remotely. The openssh is a package of open source tools for remote access of computers over the network. For this case, we will need the openssh-client and openssh-server tools.
It is likely that you already have the openssh-client installed on your host machine, but if you don’t, on Ubuntu machines it can be installed with the following command:
sudo apt install openssh-clientOn the guest side, we will need to install openssh-server with the terminal.
sudo apt install openssh-serverTo verify that everything is installed, we can use:
sudo service ssh statusAfter the installation is complete, it will be necessary to generate an SSH key pair on the host machine to authenticate to the VM.
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_vmIn this case, a key pair will be created with the name “id_rsa_vm”. When creating the keys, a passphrase for authentication will be requested. You can leave it empty if you don’t think it’s necessary. With the key generated, we can copy it to the guest machine.
ssh-copy-ip user@remote_ipTo check that was configured correctly, try to make an SSH connection to the VM from the host machine.
Using Hostnames
An alternative to the direct use of IPs for communication between machines is the use of hostnames. Hostnames are unique identifiers assigned to a device on the network, usually used to optimize communication and make it more user-friendly.
The fastest and most practical way to add hostnames on Linux is simply to create them in the “hosts” file, in the “/etc” directory.
# file: /ext/hosts
# IP-number Domain-name Hostname
<ip_number> goku.extbr0.com gokuAfter that, to perform the SSH connection, we can use the following syntax:
ssh hostname@domain-nameTransferring files with SCP
SCP (Security Copy) is one of the most used tools on Linux when it comes to file transfer between machines, since it uses the SSH protocol to send files securely and encrypted.
The SCP syntax also includes some information about the source user, in addition to some flags that complement the tool:
scp <flags> local_user@IP:dir/file remote_user@IP:dir/remote_folder/- -P: Customization of the SSH port;
- -C: Compress the data when transferring files;
- -r: Recursively transfers the directory.
The complete list of flags and syntax of this tool can be found in this article.
It is worth noting that not all attributes should be used obligatorily. For example, if we are using the command from the local user, the information of their IP can be disregarded. The simplified command can be used as:
scp extension.zip goku@goku.extbr0.com:~/In this example, we are copying the “extension.zip” to the home of the user “goku” on the machine “goku.extbr0.com”, which we configured earlier.
Automating the process with Shell Script
So far, we should be able to transfer files between machines and extract them in the correct locations. However, executing these actions several times during the application build can become an exhausting task that delays development.
To assist in the process, we can create a small Makefile that executes all the necessary actions in an automated way. A simple example of how this can be done can be seen in the following code:
# Constants for the script
EXTENSION_DIR="~/.local/share/gnome-shell/extensions/"
# Define the zipfile name
ZIPFILE="extensao.zip"
# Zip the current directory, excluding the specified files/directories
zip -r $ZIPFILE
# Copy the zip file to the VM
scp $ZIPFILE goku@goku.extbr0.com:~/
# Execute the following commands on the VM
ssh goku@goku.extbr0.com << EOF
# Unzip the zip file on the VM
unzip $ZIPFILE -d $EXTENSION_DIR$UUID
EOFDissecting it part by part, at the beginning of the code we have the definition of the file name and extension installation directory variables:
EXTENSION_DIR="~/.local/share/gnome-shell/extensions/"
ZIPFILE="extension.zip"In the following excerpt, all files are compressed into a single zip with the name defined in the ZIPFILE variable.
zip -r $ZIPFILENext, the extension is moved to the VM, in the home directory:
scp $ZIPFILE goku@goku.extbr0.com:~/With the extension transferred to the guest machine, it will now be necessary to unpack the file in the correct directory. This can be done through SSH, without the need to directly access the guest machine.
In this case, everything that will be executed on the remote machine will be in the EOF block. EOF (End Of File) is an indicator of the end of the file, used to finalize the document being built in the shell.
In the context of our project, it is used in conjunction with the “<<” operator to indicate that the shell should interpret all the lines that follow until the next EOF, closing the block of commands, which in this case is treated as input for the remote shell of the VM.
ssh goku@goku.extbr0.com << EOF
unzip $ZIPFILE -d $EXTENSION_DIR/UUID
EOFWithin the EOF block, the extension is then extracted and properly installed on the virtual machine, finalizing the connection to the VM.
Final Considerations
After the configuration, it should now be possible to build and share the extension with a virtual machine, allowing the execution of tests in a secure environment from the execution of a single script, optimizing the workflow.
We can also complement the experience by dividing the tasks into other .sh files and creating tasks in your IDE to run them with a single click. It may also be useful to install the GNOME Extensions utility to manage the extension installed on the VM.
In a future article, we will discuss ways to configure the workflow in VS Code to make the development more user-friendly.




