Elixir: Calling Sudo Commands

In Elixir, to interact with the host system or the virtual machine, we use the module System. In order to run a command on the system, we can use the function System.cmd/3:

# This function returns the calls mix deps.get command
System.cmd("mix", ["deps.get"])

System.cmd/3 is a great way to run a command from elixir layer, but it has some hurdles while trying to use a sudo command. If you try to call a sudo command with System.cmd/3 it gives the following error:

System.cmd("sudo", ["su"])
# Gives this output--> sudo: no tty present and no askpass program specified

Creating an askpass program

In order to work around this we need to provide an askpass script, which can prompt the users for their sudo password. There are any askpass applications available, but one of the most common ones is GNOME’s Zenity. Using zenity, we can ask for password using the command: $ zenity --password --title=Authentication.

I use zenity for my scripts because it has a working build for all the Operating Systems that I use (Arch Linux, Ubuntu, Dedian, Mac OSX, Pop!_OS, Fedora, CentOS and more..)

To install zenity run the following commands:

# For an arch linux user
sudo pacman -S zenity --noconfirm
# For an ubuntu, pop os user
sudo apt-get -y install zenity
# For a mac user
# Make sure HomeBrew is installed
brew install zenity
# For a fedora, centos user
yum install zenity

Now, we’re ready to use zenity.

So, let’s create a file askpass.sh with the following contents:

#!/bin/bash
zenity --password --title=Authentication

Now that we have a program which prompts for user password, we can use this while calling the sudo command with System.cmd/3.

Using askpass.sh with System.cmd/3

System.cmd/3 takes a Keyword as third parameter, which accepts the env key. This allows us to pass command-specific environment variables to the call. We can use this feature to pass the environment variable, SUDO_ASKPASS. This variable should point to the askpass.sh file, which is the program that asks for password.

One more thing, we will need to call the sudo command with -A option which specifies that it needs to use the askpass program defined by the environment variable.

I will also be adding another option, into: IO.stream(:stdio, :line) so we can see the output of the sudo command line-by-line.

The final call looks like this:

System.cmd("sudo", ["-A" , "su"],
  env: [{"SUDO_ASKPASS", "./askpass.sh"}],
  into: IO.stream(:stdio, :line))

Once invoked, this command will open a zenity window which prompts for the password and once entered, it will continue with the command’s execution.

For a more advanced usage of this check out my project, adify. This project sets up a machine with asdf, erlang, elixir and all the tools that I use (and uses the above trick to do that).