Author: Francis Ndungu
Last Updated: Fri, Oct 15, 2021The 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.
To complete this guide, you require:
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 the public_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.
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 name vhs-automator.go
to follow this guide.
$ nano vhs-automator.go
Initialize the vhs-automator.go
file and import all the libraries you will use. The flag
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 a domain
variable using the flag.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 the MkdirAll
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
. The root
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 statement os.Chown
to change the ownership of the public_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.
vhs-automator
ToolUp 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 and etc/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.
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.