Logo
blank Skip to main content

Silently Installing a Service on a Virtual Machine Using Libguestfs

Imagine that you need to install a service (a daemon) outside on a virtual machine as silently as possible while itโ€™s turned off. This method lets you install all necessary files without involving the user in the process. If this article allows you to save valuable time, then weโ€™ve achieved our goal.

Weโ€™ll share our experience so you can know how to solve a similar task.

Why do you need to use this installation method?

External hidden installation of a service on a virtual machine is a convenient method that will allow you to

  • install any service without user intervention;
  • install the same service on multiple systems.

It may be useful for installing system security or maintenance-related services, such as for user data protection or transferring data to another system.

Typically, a user turns on a system, installs the necessary software, then turns off the system. But using the method described below, you can do the same thing for multiple users without turning their systems on.

The main advantage is that installation takes place when virtual machines arenโ€™t in use, saving usersโ€™ time and not requiring them to take additional action. This method lets you install a service in a centralized manner and lets your users start working at the touch of a button, waiting only two minutes instead of the five to seven with manual installation.

This method can also be an important option when supporting individual user systems if the administrator doesnโ€™t have the opportunity to log in to a userโ€™s machine.

Sample environment

Letโ€™s consider an implementation in a Linux environment. In our example, we have Ubuntu Desktop 16.04 with a standard installation on the ext4 file system as a virtual machine for the QEMU hardware emulation system.

Ubuntu is a free distribution of the Linux operating system.

QEMU is a free, open-source virtualization tool thatโ€™s used to emulate various architectures.

ext4 (also called ext4fs) is the Fourth Extended File System. Itโ€™s a journaling file system running under the Linux operating system. Ext4 was built on the platform of its predecessor, ext3, which is used to be the default file system for many popular GNU/Linux distributions.

the main advantage in saving time

How you can do it

We donโ€™t have any ways to directly interact with the virtual machine through the network or by other means. But since itโ€™s a virtual machine, thereโ€™s a disk, which is a regular file that you can modify to add information about starting the service (daemon) and to add the executable file itself. You can mount the disk using standard QEMU tools and work with data on it. You could also parse an image file, but that may take more than a month.

In our example, weโ€™ll use the libguestfs library because it has the necessary functionality to perform this task. The utilities in libguestfs use the Linux kernel code and QEMU. They allow you to access almost any type of file system:

  • All known Linux file systems (ext2/3/4, XFS, btrfs, etc.)
  • Any Windows file system (VFAT and NTFS)
  • any macOS and BSD file system

Itโ€™s also possible to access LVM2 volumes, MBR and GPT disk partitions, raw disks, qcow2, VirtualBox VDI, VMWare VMDK, Hyper-V VHD/VHDX, files, local devices, CD and DVD ISOs, SD cards, and remote directories via FTP, HTTP, SSH, iSCSI, NBD, GlusterFS, Ceph, Sheepdog, and so on.

Libguestfs doesnโ€™t require root privileges. In addition, it can access and change images of virtual disks as well as view and edit files inside virtual machines from the outside.

Related services

Cloud Computing & Virtualization Development

Simple service

For a better understanding libguestfs implementation, letโ€™s start with writing and analyzing the code that starts a simple service (daemon). A daemon is a service of a Unix or Unix-like operating system that runs in the background without direct communication with the user.

Letโ€™s create a simple daemon that will start every five seconds to write a message to the log about the current time.

C++
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>
  
void do_heartbeat()
{
    time_t rawtime;
    struct tm * timeinfo;
    time ( &rawtime );
    timeinfo = localtime ( &rawtime );
    syslog(LOG_NOTICE, "Current local time and date: %s", asctime (timeinfo));
}
  
int main(void)
{
   pid_t pid, sid;
   pid = fork();
   if(pid > 0)
   {
      exit(EXIT_SUCCESS);
   }
   else if(pid < 0)
   {
      exit(EXIT_FAILURE);
   }
   umask(0);
   openlog("daemon-named", LOG_NOWAIT | LOG_PID, LOG_USER);
   syslog(LOG_NOTICE, "Successfully started daemon-name");
   sid = setsid();
   if(sid < 0)
   {
      syslog(LOG_ERR, "Could not generate session ID for child process");
      exit(EXIT_FAILURE);
   }
   if((chdir("/")) < 0)
   {
      syslog(LOG_ERR, "Could not change working directory to /");
      exit(EXIT_FAILURE);
   }
   close(STDIN_FILENO);
   close(STDOUT_FILENO);
   close(STDERR_FILENO);
   const int SLEEP_INTERVAL = 5;
   while(1)
   {
      do_heartbeat();
      sleep(SLEEP_INTERVAL);
   }
   syslog(LOG_NOTICE, "Stopping daemon-name");
   closelog();
   exit(EXIT_SUCCESS);
}

We also need the systemd service file to run the script when the system boots. Hereโ€™s an example of a configuration file:

ShellScript
[Unit]
Description= Simple daemon
 [Service]
Type=simple
ExecStart=/usr/bin/daemon
 [Install]
WantedBy=multi-user.target

Now that we have the daemon and config files, letโ€™s run and test our prototype. Guestfish, which is included in the libguestfs-tools package, provides us with a handy utility with a command-line interpreter. Weโ€™ll outline a simple script that will install our service on a powered off virtual machine.

ShellScript
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT=$(guestfish -d generic -i inspect-os)
echo "root disk   : $ROOT"
guestfish -d generic <<_EOF_ 
run
mount $ROOT /
copy-in $DIR/daemon/usr/bin/
copy-in $DIR/simple-daemon.service/etc/systemd/system/
_EOF_ 

In the service log, you can see that the service has appeared in the system and the executable file is in the right place, /usr/bin/daemon, but it doesnโ€™t work and is disabled.

ShellScript
\u25cf simple-daemon.service - Super simple daemon
   Loaded: loaded (/etc/systemd/system/simple-daemon.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

Usually, these are the commands to install the daemon in the system:

ShellScript
systemctl daemon-reload
systemctl enable simple-daemon.service
systemctl start simple-daemon.service

But the system is disabled by daemon-reload, which we donโ€™t need, which means that we only need to enable a simple-daemon.service. There are two ways to do this. The first is to enable systemctl, and the second is to see how this operation works and implement it using file operations. As it turns out, the enable operation simply creates a symbolic link, which means that now only one line should be added to the script.

ShellScript
ln-s /etc/systemd/system/simple-daemon.service 
/etc/systemd/system/multi-user.target.wants/simple-daemon.service
unmount /

This is what launching a command in guestfish looks like in our case:

ShellScript
command "/bin/systemctl enable simple-daemon.service"
unmount /

After this, we run the script and check the result. Everything works perfectly. Just put the script in order, add some beauty, and hereโ€™s the result:

ShellScript
  #!/bin/bash
if [ "$(whoami)" != "root" ]; then
    echo "Sorry, you are not root."
    exit 1
fi
RED='\033[0;31m'
GREEN='\033[0;32m'
RESET='\033[0m' # No Color
SYSTEMD="/etc/systemd/system"
DAEMON_NAME="simple-daemon.service"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT=$(guestfish -d generic -i inspect-os)
res=$?
if [[ $res -eq 0 ]]; then
    echo "root disk   : $ROOT"
else
    echo "root disk   : ${RED}failed${RESET}"
    exit 1
fi
 guestfish -d generic <<_EOF_
run
mount $ROOT /
copy-in $DIR/daemon /usr/bin/
copy-in $DIR/$DAEMON_NAME $SYSTEMD/
ln-s $SYSTEMD/$DAEMON_NAME $SYSTEMD/multi-user.target.wants/$DAEMON_NAME
unmount /
_EOF_
  
res=$?
if [[ $res -eq 0 ]]; then
    echo -e "installation: ${GREEN}complete${RESET}"
else
    echo -e "installation: ${RED}failed${RESET}"
installing system security or maintenance

Final program

In the end, we need to write a program that will perform the actions described in the script. The final program might look like this:

C++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <guestfs.h>
void usage(void)
{
    fprintf(stderr,
        "Usage: ./install uuid\n"
        "\n"
        "NOTE: running on live virtual machine or some filesystems could corrupt disk.\n"
        "\n");
}
  
void get_full_path(char *argv[], char (*abs_exe_path)[PATH_MAX])
{
    char *p = NULL;
    char path_save[PATH_MAX];
      
    if(!(p = strrchr(argv[0], '/')))
    {
        getcwd(*abs_exe_path, sizeof(*abs_exe_path));
    } else
    {
        *p = '\0';
        getcwd(path_save, sizeof(path_save));
        chdir(argv[0]);
        getcwd(*abs_exe_path, sizeof(*abs_exe_path));
        chdir(path_save);
    }
}
  
int main(int argc, char *argv[])
{
    guestfs_h *g = NULL;
    int res = EXIT_SUCCESS;
    const char *uuid = NULL;
    struct guestfs_add_domain_argv options = {0};
    char **list = NULL, **ptr = NULL, **os_root = NULL, *p = NULL;
    char systemd_conf_path[PATH_MAX] = {0}, systemd_conf_link_path[PATH_MAX] = {0};
    char abs_exe_path[PATH_MAX] = {0}, daemon_path[PATH_MAX] = {0}, daemon_conf_path[PATH_MAX] = {0};
    char daemon_name[] = "daemon";
    char daemon_conf[] = "simple-daemon.service";
    char bin_path[] = "/usr/bin/";
    char systemd_path[] = "/etc/systemd/system/";
    if (argc != 2) {
        usage();
        exit(EXIT_FAILURE);
    }
    uuid = argv[1];
    fflush(stdout);
    /* Guestfs handle. */
    g = guestfs_create();
    if (g == NULL) {
        perror("could not create libguestfs handle");
        exit(EXIT_FAILURE);
    }
    /* Add the named domain. */
    if (guestfs_add_domain_argv(g, uuid, &options) == -1)
    {
        exit(EXIT_FAILURE);
    }
    if (guestfs_launch(g) == -1)
    {
        perror("could launch");
        exit(EXIT_FAILURE);
    }
    /* inspect os, get root disk */
    os_root = guestfs_inspect_os(g);
    if (os_root == NULL)
    {
        perror("could not inspect operation system");
        res = EXIT_FAILURE;
        goto close;
    }
    printf("root disk:\t%s\n", *os_root);
    /* Mount the domain filesystem. */
    if (guestfs_mount(g, *os_root, "/") == -1)
    {
        perror("could not mount filesystem");
        res = EXIT_FAILURE;
        goto close;
    }
    /* set path variable */
    get_full_path(argv, &abs_exe_path);
    strcpy(daemon_path, abs_exe_path);
    strcat(daemon_path, "/");
    strcat(daemon_path, daemon_name);
    strcpy(daemon_conf_path, abs_exe_path);
    strcat(daemon_conf_path, "/");
    strcat(daemon_conf_path, daemon_conf);
    strcpy(systemd_conf_path, systemd_path);
    strcat(systemd_conf_path, daemon_conf);
    strcpy(systemd_conf_link_path, systemd_path);
    strcat(systemd_conf_link_path, "multi-user.target.wants/");
    strcat(systemd_conf_link_path, daemon_conf);
    /* copy file */
    if (guestfs_copy_in(g, daemon_path, bin_path) == -1) {
        perror("could not copy daemon to guest filesystem");
        res = EXIT_FAILURE;
        goto unmount;
    }
    if (guestfs_copy_in(g, daemon_conf_path, systemd_path) == -1) {
        perror("could not copy daemon config to guest filesystem");
        res = EXIT_FAILURE;
        goto unmount;
    }
    /* make link */
    if (guestfs_ln_s(g, systemd_conf_path, systemd_conf_link_path) == -1) {
        perror("could not make config link");
        res = EXIT_FAILURE;
        goto unmount;
    }
unmount:
    printf("installation:\t%s\n", (res == EXIT_SUCCESS ? "complete" : "failed"));
    guestfs_umount(g, "/");
close:
    guestfs_close(g);
    exit(res);
}

Conclusion

After all these uncomplicated manipulations, weโ€™ve installed the service (a daemon) in a way thatโ€™s completely imperceptible to the virtual machine user, although to do this we still needed access to the host. This method is quite versatile and is easier than creating many installation packages and maintaining them. This is just a simple introductory example of the large and powerful libguestfs library.

Virtualization-related tasks on various platforms are one of Aprioritโ€™s specialties. While building virtualization software solutions, our team has accumulated a number of useful tips and recipes that we would be glad to share with you.

Have a question?

Ask our expert!

Tell us about your project

Send us a request for proposal! Weโ€™ll get back to you with details and estimations.

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting?

Get a quick Apriorit intro to better understand our team capabilities.

Book time slot

Contact us