Introduction
The Golang programming language is suitable for creating fast and reliable cross-platform command-line interfaces. Typically, you code these types of scripts/packages to solve a particular problem in your Linux server. For instance, if you want to run multiple websites with Apache on your single server, the process of manually configuring the virtual hosts is tedious and time-consuming. A Golang CLI tool can automate the process for you.
When you code and compile CLI tools with Golang, you reap the benefits of fast binaries that are easier to distribute to end-users. In addition, the Golang flag
library allows you to use and parse command-line arguments to customize how your final tools work.
In this guide, you'll create an elegant CLI tool with Golang to automate the process of creating virtual hosts with Apache on your Ubuntu 20.04 server.
Prerequisites
To complete this guide, you require:
1. The Process of Creating Virtual Hosts Manually
When you want to host multiple websites with Apache on a single server, you set up directories for each website, assign the correct file permissions, and set up a new virtual host configuration file. For instance, here is a detailed manual process that you can use to add the domain example.com
to your server.
Make a directory for the
example.com
website and set the correct permissions for thepublic_html
sub-directory.$ sudo mkdir -p /var/www/example.com/public_html $ sudo chmod -R 755 /var/www/example.com/public_html
Take ownership of the new
/var/www/example.com/public_html
directory.$ sudo chown -R $USER:$USER /var/www/example.com/public_html
Create a virtual host file under the
/etc/apache2/sites-available/
directory.$ sudo nano /etc/apache2/sites-available/example.com.conf
Paste the information below into the
example.com.conf
file.<VirtualHost *:80> ServerAdmin admin@example.com ServerName example.com DocumentRoot /var/www/example.com/public_html <Directory /var/www/example.com/public_html> Options -Indexes +FollowSymLinks -MultiViews AllowOverride All Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>
Save and close the file.
Disable the default virtual host file.
$ sudo a2dissite 000-default.conf
Enable the new
example.com.conf
virtual host file.$ sudo a2ensite example.com.conf
Restart Apache to load the new virtual host.
$ sudo systemctl restart apache2
As you can see, the above manual process is quite long and sometimes prone to errors if you make a typing mistake when configuring the directories and the configuration files. Also, only a seasoned Linux administrator can understand and follow the process.
In the next step, you'll create a Golang CLI tool that allows you to add a virtual host to the Apache web server by executing a single command with the domain name as an argument.
2. Creating a Virtual Host CLI Tool with Golang
SSH to your server to complete the following steps.
Create a
project
directory under your home directory.$ mkdir project
Switch to the new
project
directory.$ cd project
Open a new
vhs-automator.go
file. You can name your CLI tool any name but for now, stick to the namevhs-automator.go
to follow this guide.$ nano vhs-automator.go
Initialize the
vhs-automator.go
file and import all the libraries you will use. Theflag
library is the main candidate here, and it allows you to accept and parse command-line flags and arguments.package main import ( "flag" "fmt" "os" "os/exec" "os/user"
"strconv" "io/ioutil" )
Next, add the
main()
function and initialize adomain
variable using theflag.String()
method. If an end-user doesn't enter a value for the domain, the script should exit with an error code.func main() { domain := flag.String("domain", "", "Enter the virtual host domain name.(Required)") flag.Parse() if *domain == "" { flag.PrintDefaults() os.Exit(1) }
Next, use the
os
library and theMkdirAll
method to create a directory for the domain under the/var/www
directory. Here, you're retrieving the domain name from the command line as entered by the user using the syntax*domain
. Theroot
user owns the directories you're creating here.fmt.Println("Creating directory and permissions for the new domain. \n") err := os.MkdirAll("/var/www/" + *domain + "/public_html", 0755) if err != nil { fmt.Println(err) os.Exit(1) } else { fmt.Println("Directory and permissions for the " + *domain + " created successfully. \n") }
Next, use the statement
user.Lookup(os.Getenv("SUDO_USER")
to get the current User ID and Group ID of the user running the command. Then, use the statementos.Chown
to change the ownership of thepublic_html
to the current user.usr, err := user.Lookup(os.Getenv("SUDO_USER")) if err != nil { fmt.Println(err) os.Exit(1) } Uid, err := strconv.ParseInt(usr.Uid, 10, 64) user_id := int(Uid) Gid, err := strconv.ParseInt(usr.Gid, 10, 64) group_id := int(Gid) fmt.Println("Setting file ownership for the new domain. \n") err = os.Chown("/var/www/" + *domain + "/public_html", user_id, group_id) if err != nil { fmt.Println(err) os.Exit(1) } else { fmt.Println("File owernship for the new domain set successfully. \n") }
Next, create a virtual host file with the parameters of the new domain name using the Golang
ioutil
library.vh_settings := `<VirtualHost *:80>` + "\n\n" + ` ServerAdmin admin@` + *domain + "\n" + ` ServerName ` + *domain + "\n" + ` DocumentRoot /var/www/` + *domain + `/public_html` + "\n\n" + ` <Directory /var/www/` + *domain + `/public_html>` + "\n" + ` Options -Indexes +FollowSymLinks -MultiViews` + "\n" + ` AllowOverride All ` + "\n" + ` Require all granted` + "\n" + ` </Directory>` + "\n\n" + ` ErrorLog ${APACHE_LOG_DIR}/error.log` + "\n" + ` CustomLog ${APACHE_LOG_DIR}/access.log combined` + "\n\n" + ` </VirtualHost>` conf_data := []byte(vh_settings) fmt.Println("Creating virtual host configuration file... \n") err = ioutil.WriteFile("/etc/apache2/sites-available/" + *domain + ".conf", conf_data, 0644) if err != nil { fmt.Println(err) os.Exit(1) } else { fmt.Println("Virtual host configuration file created. \n") }
Use the
exec.Command
to enable the new virtual host configuration file and restart the apache web server.fmt.Println("Enabling " + *domain + ".conf file...\n") cmd := exec.Command("a2ensite", *domain + ".conf") stdout, err := cmd.Output() if err != nil { fmt.Println(err.Error()) os.Exit(1) } else { fmt.Print(string(stdout)) fmt.Println(*domain + ".conf configuration file enabled successfully. \n") } fmt.Println("Restarting Apache...\n") cmd = exec.Command("systemctl", "restart", "apache2") stdout, err = cmd.Output() if err != nil { fmt.Println(err.Error()) os.Exit(1) } else { fmt.Print(string(stdout)) fmt.Println("Apache server restarted successfully. \n") } }
After adding all the code blocks, your
vhs-automator.go
file should be similar to:package main import ( "flag" "fmt" "os" "os/exec" "os/user"
"strconv" "io/ioutil" )
func main() { domain := flag.String("domain", "", "Enter the virtual host domain name.(Required)") flag.Parse() if *domain == "" { flag.PrintDefaults() os.Exit(1) } fmt.Println("Creating directory and permissions for the new domain. \n") err := os.MkdirAll("/var/www/" + *domain + "/public_html", 0755) if err != nil { fmt.Println(err) os.Exit(1) } else { fmt.Println("Directory and permissions for the " + *domain + " created successfully. \n") } usr, err := user.Lookup(os.Getenv("SUDO_USER")) if err != nil { fmt.Println(err) os.Exit(1) } Uid, err := strconv.ParseInt(usr.Uid, 10, 64) user_id := int(Uid) Gid, err := strconv.ParseInt(usr.Gid, 10, 64) group_id := int(Gid) fmt.Println("Setting file ownership for the new domain. \n") err = os.Chown("/var/www/" + *domain + "/public_html", user_id, group_id) if err != nil { fmt.Println(err) os.Exit(1) } else { fmt.Println("File owernship for the new domain set successfully. \n") } vh_settings := `<VirtualHost *:80>` + "\n\n" + ` ServerAdmin admin@` + *domain + "\n" + ` ServerName ` + *domain + "\n" + ` DocumentRoot /var/www/` + *domain + `/public_html` + "\n\n" + ` <Directory /var/www/` + *domain + `/public_html>` + "\n" + ` Options -Indexes +FollowSymLinks -MultiViews` + "\n" + ` AllowOverride All ` + "\n" + ` Require all granted` + "\n" + ` </Directory>` + "\n\n" + ` ErrorLog ${APACHE_LOG_DIR}/error.log` + "\n" + ` CustomLog ${APACHE_LOG_DIR}/access.log combined` + "\n\n" + ` </VirtualHost>` conf_data := []byte(vh_settings) fmt.Println("Creating virtual host configuration file... \n") err = ioutil.WriteFile("/etc/apache2/sites-available/" + *domain + ".conf", conf_data, 0644) if err != nil { fmt.Println(err) os.Exit(1) } else { fmt.Println("Virtual host configuration file created. \n") } fmt.Println("Enabling " + *domain + ".conf file...\n") cmd := exec.Command("a2ensite", *domain + ".conf") stdout, err := cmd.Output() if err != nil { fmt.Println(err.Error()) os.Exit(1) } else { fmt.Print(string(stdout)) fmt.Println(*domain + ".conf configuration file enabled successfully. \n") } fmt.Println("Restarting Apache...\n") cmd = exec.Command("systemctl", "restart", "apache2") stdout, err = cmd.Output() if err != nil { fmt.Println(err.Error()) os.Exit(1) } else { fmt.Print(string(stdout)) fmt.Println("Apache server restarted successfully. \n") } }
Save and close the file when you're through with editing.
3. Testing and Compiling the vhs-automator
Tool
Up to this point, you can now use your vhs-automator.go
script to add new virtual domains on your server.
Make sure the default virtual host is disabled. This is the only command that you've to run manually since you should do it once throughout the lifetime of your server.
$ sudo a2dissite 000-default.conf
Then, add the 3 sample domains by executing the commands below. Your domain names should follow the
-domain
flag.$ sudo go run vhs-automator.go -domain example.com $ sudo go run vhs-automator.go -domain example.net $ sudo go run vhs-automator.go -domain example.org
To confirm whether your tool is working as expected, you may create sample content under the
public_html
directory of each domain by running the command below.$ echo 'The example.com is working' | sudo tee /var/www/example.com/public_html/index.html $ echo 'The example.net is working' | sudo tee /var/www/example.net/public_html/index.html $ echo 'The example.org is working' | sudo tee /var/www/example.org/public_html/index.html
If you've pointed the domain names to your server's public IP address, you can now visit the URLs below to check if everything is working as expected. Also, you can edit your hosts' file (That is
C:\Windows\System32\drivers\etc\hosts
in windows andetc/hosts
in Linux) and point the domain names above to your server's public IP address to test if the configurations are working as expected.Sample outputs from the browser.
The example.com is working The example.net is working The example.org is working
Build the
vhs-automator.go
package.$ sudo go build vhs-automator.go
You should now have a binary with the following name in your current
project
directory.vhs-automator
Add the binary to the
/usr/local/bin
directory. This allows you to execute the binary just by entering its name in the Linux shell.$ sudo cp vhs-automator /usr/local/bin/vhs-automator
Execute the
vhs-automator
binary. This time around, create another domain. For instance,anydomain.example
.$ sudo vhs-automator -domain anydomain.example
You should receive the following response as your
anydomain.example
is created on the server.Creating directory and permissions for the new domain. Directory and permissions for the anydomain.example created successfully. Setting file ownership for the new domain. File owernship for the new domain set successfully. Creating virtual host configuration file... Virtual host configuration file created. Enabling anydomain.example.conf file... Enabling site anydomain.example. To activate the new configuration, you need to run: systemctl reload apache2 anydomain.example.conf configuration file enabled successfully. Restarting Apache... Apache server restarted successfully.
Create sample content for the new website.
$ echo 'The anydomain.example is working' | sudo tee /var/www/anydomain.example/public_html/index.html
If you now visit the domain http://anydomain.example on a browser, you should get the following output.
The anydomain.example is working
Your Golang script is now working as expected.
Conclusion
In this guide, you've created a command-line interface tool with Golang to automate the process of adding Apache virtual hosts on your Ubuntu 20.04 server. Use the knowledge in this guide to create powerful CLI tools with Golang.