One place for hosting & domains

      How To Set Up a Remote Database to Optimize Site Performance with MySQL on Ubuntu 18.04


      Introduction

      As your application or website grows, there may come a point where you’ve outgrown your current server setup. If you are hosting your web server and database backend on the same machine, it may be a good idea to separate these two functions so that each can operate on its own hardware and share the load of responding to your visitors’ requests.

      In this guide, we’ll go over how to configure a remote MySQL database server that your web application can connect to. We will use WordPress as an example in order to have something to work with, but the technique is widely applicable to any application backed by MySQL.

      Prerequisites

      Before beginning this tutorial, you will need:

      • Two Ubuntu 18.04 servers. Each should have a non-root user with sudo privileges and a UFW firewall enabled, as described in our Initial Server Setup with Ubuntu 18.04 tutorial. One of these servers will host your MySQL backend, and throughout this guide we will refer to it as the database server. The other will connect to your database server remotely and act as your web server; likewise, we will refer to it as the web server over the course of this guide.
      • Nginx and PHP installed on your web server. Our tutorial How To Install Linux, Nginx, MySQL, PHP (LEMP stack) in Ubuntu 18.04 will guide you through the process, but note that you should skip Step 2 of this tutorial, which focuses on installing MySQL, as you will install MySQL on your database server.
      • MySQL installed on your database server. Follow “How To Install MySQL on Ubuntu 18.04” to set this up.
      • Optionally (but strongly recommended), TLS/SSL certificates from Let’s Encrypt installed on your web server. You’ll need to purchase a domain name and have DNS records set up for your server, but the certificates themselves are free. Our guide How To Secure Nginx with Let’s Encrypt on Ubuntu 18.04 will show you how to obtain these certificates.

      Step 1 — Configuring MySQL to Listen for Remote Connections

      Having one’s data stored on a separate server is a good way to expand gracefully after hitting the performance ceiling of a one-machine configuration. It also provides the basic structure necessary to load balance and expand your infrastructure even more at a later time. After installing MySQL by following the prerequisite tutorial, you’ll need to change some configuration values to allow connections from other computers.

      Most of the MySQL server’s configuration changes can be made in the mysqld.cnf file, which is stored in the /etc/mysql/mysql.conf.d/ directory by default. Open up this file with root privileges in your preferred editor. Here, we’ll use nano:

      • sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

      This file is divided into sections denoted by labels in square brackets ([ and ]). Find the section labeled mysqld:

      /etc/mysql/mysql.conf.d/mysqld.cnf

      . . .
      [mysqld]
      . . .
      

      Within this section, look for a parameter called bind-address. This tells the database software which network address to listen for connections on.

      By default, this is set to 127.0.0.1, meaning that MySQL is configured to only look for local connections. You need to change this to reference an external IP address where your server can be reached.

      If both of your servers are in a datacenter with private networking capabilities, use your database server’s private network IP. Otherwise, you can use its public IP address:

      /etc/mysql/mysql.conf.d/mysqld.cnf

      [mysqld]
      . . .
      bind-address = db_server_ip
      

      Because you’ll connect to your database over the internet, it’s recommended that you require encrypted connections to keep your data secure. If you don’t encrypt your MySQL connection, anybody on the network could sniff sensitive information between your web and database servers. To encrypt MySQL connections, add the following line after the bind-address line you just updated:

      /etc/mysql/mysql.conf.d/mysqld.cnf

      [mysqld]
      . . .
      require_secure_transport = on
      . . .
      

      Save and close the file when you are finished. If you’re using nano, do this by pressing CTRL+X, Y, and then ENTER.

      For SSL connections to work, you will need to create some keys and certificates. MySQL comes with a command that will automatically set these up. Run the following command, which creates the necessary files. It also makes them readable by the MySQL server by specifying the UID of the mysql user:

      • sudo mysql_ssl_rsa_setup --uid=mysql

      To force MySQL to update its configuration and read the new SSL information, restart the database:

      • sudo systemctl restart mysql

      To confirm that the server is now listening on the external interface, run the following netstat command:

      • sudo netstat -plunt | grep mysqld

      Output

      tcp 0 0 db_server_ip:3306 0.0.0.0:* LISTEN 27328/mysqld

      netstat prints statistics about your server’s networking system. This output shows us that a process called mysqld is attached to the db_server_ip at port 3306, the standard MySQL port, confirming that the server is listening on the appropriate interface.

      Next, open up that port on the firewall to allow traffic through:

      Those are all the configuration changes you need to make to MySQL. Next, we will go over how to set up a database and some user profiles, one of which you will use to access the server remotely.

      Step 2 — Setting Up a WordPress Database and Remote Credentials

      Even though MySQL itself is now listening on an external IP address, there are currently no remote-enabled users or databases configured. Let's create a database for WordPress, and a pair of users that can access it.

      Begin by connecting to MySQL as the root MySQL user:

      Note: If you have password authentication enabled, as described in Step 3 of the prerequisite MySQL tutorial, you will instead need to use the following command to access the MySQL shell:

      After running this command, you will be asked for your MySQL root password and, after entering it, you'll be given a new mysql> prompt.

      From the MySQL prompt, create a database that WordPress will use. It may be helpful to give this database a recognizable name so that you can easily identify it later on. Here, we will name it wordpress:

      • CREATE DATABASE wordpress;

      Now that you've created your database, you next need to create a pair of users. We will create a local-only user as well as a remote user tied to the web server’s IP address.

      First, create your local user, wordpressuser, and make this account only match local connection attempts by using localhost in the declaration:

      • CREATE USER 'wordpressuser'@'localhost' IDENTIFIED BY 'password';

      Then grant this account full access to the wordpress database:

      • GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpressuser'@'localhost';

      This user can now do any operation on the database for WordPress, but this account cannot be used remotely, as it only matches connections from the local machine. With this in mind, create a companion account that will match connections exclusively from your web server. For this, you'll need your web server's IP address.

      Please note that you must use an IP address that utilizes the same network that you configured in your mysqld.cnf file. This means that if you specified a private networking IP in the mysqld.cnf file, you'll need to include the private IP of your web server in the following two commands. If you configured MySQL to use the public internet, you should match that with the web server's public IP address.

      • CREATE USER 'wordpressuser'@'web-server_ip' IDENTIFIED BY 'password';

      After creating your remote account, give it the same privileges as your local user:

      • GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpressuser'@'web_server_ip';

      Lastly, flush the privileges so MySQL knows to begin using them:

      Then exit the MySQL prompt by typing:

      Now that you've set up a new database and a remote-enabled user, you can move on to testing whether you're able to connect to the database from your web server.

      Step 3 — Testing Remote and Local Connections

      Before continuing, it's best to verify that you can connect to your database from both the local machine — your database server — and from your web server with each of the wordpressuser accounts.

      First, test the local connection from your database server by attempting to log in with your new account:

      • mysql -u wordpressuser -p

      When prompted, enter the password that you set up for this account.

      If you are given a MySQL prompt, then the local connection was successful. You can exit out again by typing:

      Next, log into your web server to test remote connections:

      You'll need to install some client tools for MySQL on your web server in order to access the remote database. First, update your local package cache if you haven't done so recently:

      Then install the MySQL client utilities:

      • sudo apt install mysql-client

      Following this, connect to your database server using the following syntax:

      • mysql -u wordpressuser -h db_server_ip -p

      Again, you must make sure that you are using the correct IP address for the database server. If you configured MySQL to listen on the private network, enter your database's private network IP. Otherwise, enter your database server's public IP address.

      You will be asked for the password for your wordpressuser account. After entering it, and if everything is working as expected, you will see the MySQL prompt. Verify that the connection is using SSL with the following command:

      If the connection is indeed using SSL, the SSL: line will indicate this, as shown here:

      Output

      -------------- mysql Ver 14.14 Distrib 5.7.18, for Linux (x86_64) using EditLine wrapper Connection id: 52 Current database: Current user: wordpressuser@203.0.113.111 SSL: Cipher in use is DHE-RSA-AES256-SHA Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.18-0ubuntu0.16.04.1 (Ubuntu) Protocol version: 10 Connection: 203.0.113.111 via TCP/IP Server characterset: latin1 Db characterset: latin1 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 3 hours 43 min 40 sec Threads: 1 Questions: 1858 Slow queries: 0 Opens: 276 Flush tables: 1 Open tables: 184 Queries per second avg: 0.138 --------------

      After verifying that you can connect remotely, go ahead and exit the prompt:

      With that, you've verified local access and access from the web server, but you have not verified that other connections will be refused. For an additional check, try doing the same thing from a third server for which you did not configure a specific user account in order to make sure that this other server is not granted access.

      Note that before running the following command to attempt the connection, you may have to install the MySQL client utilities as you did above:

      • mysql -u wordpressuser -h db_server_ip -p

      This should not complete successfully, and should throw back an error that looks similar to this:

      Output

      ERROR 1130 (HY000): Host '203.0.113.12' is not allowed to connect to this MySQL server

      This is expected, since you haven't created a MySQL user that's allowed to connect from this server, and also desired, since you want to be sure that your database server will deny unauthorized users access to your MySQL server.

      After successfully testing your remote connection, you can proceed to installing WordPress on your web server.

      Step 4 — Installing WordPress

      To demonstrate the capabilities of your new remote-capable MySQL server, we will go through the process of installing and configuring WordPress — the popular content management system — on your web server. This will require you to download and extract the software, configure your connection information, and then run through WordPress's web-based installation.

      On your web server, download the latest release of WordPress to your home directory:

      • cd ~
      • curl -O https://wordpress.org/latest.tar.gz

      Extract the files, which will create a directory called wordpress in your home directory:

      WordPress includes a sample configuration file which we'll use as a starting point. Make a copy of this file, removing -sample from the filename so it will be loaded by WordPress:

      • cp ~/wordpress/wp-config-sample.php ~/wordpress/wp-config.php

      When you open the file, your first order of business will be to adjust some secret keys to provide more security to your installation. WordPress provides a secure generator for these values so that you do not have to try to come up with good values on your own. These are only used internally, so it won't hurt usability to have complex, secure values here.

      To grab secure values from the WordPress secret key generator, type:

      • curl -s https://api.wordpress.org/secret-key/1.1/salt/

      This will print some keys to your output. You will add these to your wp-config.php file momentarily:

      Warning! It is important that you request your own unique values each time. Do not copy the values shown here!

      Output

      define('AUTH_KEY', 'L4|2Yh(giOtMLHg3#] DO NOT COPY THESE VALUES %G00o|te^5YG@)'); define('SECURE_AUTH_KEY', 'DCs-k+MwB90/-E(=!/ DO NOT COPY THESE VALUES +WBzDq:7U[#Wn9'); define('LOGGED_IN_KEY', '*0kP!|VS.K=;#fPMlO DO NOT COPY THESE VALUES +&[%8xF*,18c @'); define('NONCE_KEY', 'fmFPF?UJi&(j-{8=$- DO NOT COPY THESE VALUES CCZ?Q+_~1ZU~;G'); define('AUTH_SALT', '@qA7f}2utTEFNdnbEa DO NOT COPY THESE VALUES t}Vw+8=K%20s=a'); define('SECURE_AUTH_SALT', '%BW6s+d:7K?-`C%zw4 DO NOT COPY THESE VALUES 70U}PO1ejW+7|8'); define('LOGGED_IN_SALT', '-l>F:-dbcWof%4kKmj DO NOT COPY THESE VALUES 8Ypslin3~d|wLD'); define('NONCE_SALT', '4J(<`4&&F (WiK9K#] DO NOT COPY THESE VALUES ^ZikS`es#Fo:V6');

      Copy the output you received to your clipboard, then open the configuration file in your text editor:

      • nano ~/wordpress/wp-config.php

      Find the section that contains the dummy values for those settings. It will look something like this:

      /wordpress/wp-config.php

      . . .
      define('AUTH_KEY',         'put your unique phrase here');
      define('SECURE_AUTH_KEY',  'put your unique phrase here');
      define('LOGGED_IN_KEY',    'put your unique phrase here');
      define('NONCE_KEY',        'put your unique phrase here');
      define('AUTH_SALT',        'put your unique phrase here');
      define('SECURE_AUTH_SALT', 'put your unique phrase here');
      define('LOGGED_IN_SALT',   'put your unique phrase here');
      define('NONCE_SALT',       'put your unique phrase here');
      . . .
      

      Delete those lines and paste in the values you copied from the command line.

      Next, enter the connection information for your remote database. These configuration lines are at the top of the file, just above where you pasted in your keys. Remember to use the same IP address you used in your remote database test earlier:

      /wordpress/wp-config.php

      . . .
      /** The name of the database for WordPress */
      define('DB_NAME', 'wordpress');
      
      /** MySQL database username */
      define('DB_USER', 'wordpressuser');
      
      /** MySQL database password */
      define('DB_PASSWORD', 'password');
      
      /** MySQL hostname */
      define('DB_HOST', 'db_server_ip');
      . . .
      

      And finally, anywhere in the file, add the following line which tells WordPress to use an SSL connection to our MySQL database:

      /wordpress/wp-config.php

      define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);
      

      Save and close the file.

      Next, copy the files and directories found in your ~/wordpress directory to Nginx's document root. Note that this command includes the -a flag to make sure all the existing permissions are carried over:

      • sudo cp -a ~/wordpress/* /var/www/html

      After this, the only thing left to do is modify the file ownership. Change the ownership of all the files in the document root over to www-data, Ubuntu's default web server user:

      • sudo chown -R www-data:www-data /var/www/html

      With that, WordPress is installed and you're ready to run through its web-based setup routine.

      Step 5 — Setting Up WordPress Through the Web Interface

      WordPress has a web-based setup process. As you go through it, it will ask a few questions and install all the tables it needs in your database. Here, we will go over the initial steps of setting up WordPress, which you can use as a starting point for building your own custom website that uses a remote database backend.

      Navigate to the domain name (or public IP address) associated with your web server:

      http://example.com
      

      You will see a language selection screen for the WordPress installer. Select the appropriate language and click through to the main installation screen:

      WordPress install screen

      Once you have submitted your information, you will need to log into the WordPress admin interface using the account you just created. You will then be taken to a dashboard where you can customize your new WordPress site.

      Conclusion

      By following this tutorial, you've set up a MySQL database to accept SSL-protected connections from a remote WordPress installation. The commands and techniques used in this guide are applicable to any web application written in any programming language, but the specific implementation details will differ. Refer to your application or language's database documentation for more information.



      Source link

      How to Start a Review Site With WordPress


      Reviews have become ubiquitous online, and it’s not hard to see why. Both professional and user reviews provide first-hand information that can help you make informed purchasing decisions. The best part is that anyone with some writing skills and passion can start a review site for themselves.

      A review site is one of the best ways you can use your knowledge and interests to create valuable content. By reviewing products in a particular niche, you can be creative while leveraging your expertise to help readers find the best solutions and services. Plus, you can even earn a decent income at the same time.

      In this article, we’ll talk about the basics of a review site and discuss why you should consider starting one. We’ll also show you what’s needed to make it successful and talk about how you can enhance it using the right theme and plugins. Let’s get to work!

      A Brief Introduction to Review Sites

      An example of a review on AllMusic.com.

      What do you do when you’re considering buying a particular product, but you’re not sure it’s right for you or worth the money? Like many people, you most likely seek out reviews to help answer your questions. Whether these are written by individual users or provided on dedicated sites, they can be immensely helpful when you’re trying to make informed decisions.

      Sites dedicated to offering reviews are aptly referred to as review sites. While this moniker is accurate, it’s also somewhat vague, as it refers to a variety of websites. For example, some sites aggregate many different people’s reviews. Rotten Tomatoes fits into this category, as it combines film reviews from professional critics and users to create an average score for each movie.

      Similarly, sites like TripAdvisor are entirely devoted to user reviews of hotels and other establishments.

      Two user reviews from TripAdvisor.

      However, a review site could also feature content created by one or more specific writers. These sites function similarly to print magazines, in that they usually have a roster of hired authors or freelancers who produce content. They can also vary widely in scope and subject matter, from huge international brands like Eurogamer to one-person operations such as Wake Up For Makeup.

      This broad spectrum of possibilities means it’s both possible and easy for pretty much anyone to create their own review site. We’ll be showing you how to do just that throughout this article.

      The Benefits of Running a Review Site

      In many cases, the main reason you would want to start your own review site is simply that you enjoy the work. Most sites of this nature are run by people with a passion for a particular topic. However, review sites offer a number of more practical benefits as well.

      For one, review sites can be excellent at driving traffic. We mentioned earlier that a lot of people will go looking for reviews before making purchases. So if you can write content that is clear, engaging, and authoritative, you’ll be primed to receive plenty of visitors.

      One of the reasons for this popularity is that review sites are uniquely suited to Search Engine Optimization (SEO). That’s because your posts will almost by default match the keywords users are most likely to search for.

      For example, imagine that you run a review blog about WordPress plugins, and you write a post about Contact Form 7. You’ll most likely name it something to the effect of “Contact Form 7 – Review.” Someone curious about this plugin is most likely going to use a very similar search phrase, such as “contact form 7 review,” which makes it a lot more probable that they’ll stumble across your article.

      In addition to the SEO benefits, a review site also provides you with a lot of freedom over how you structure and display your reviews. You could make your site very basic and just use a standard blog interface, which is familiar to many people and easy to maintain.

      One example of this in action is IsItWP.

      The IsItWP.com home page.However, you could also go bigger and create a more structurally-complex site with an advanced scoring system, multiple reviews per product, and more. For example, HostingAdvice offers granular scores for added precision.

      An example of a review of DreamHost on HostingAdvice.com.

      Finally, a review site is particularly well-suited to being monetized. You have many options — such as including affiliate links alongside your reviews or featuring paid advertisements that are separate from your main content.

      However, it’s critical to remember that you don’t have to (and, in fact, shouldn’t) change the content of your reviews to suit your advertisers. If you’re not honest and frank with your users about the products you’re reviewing, they aren’t going to trust you and won’t stick around for long.

      What to Consider Before Starting a Review Site

      Before you start sharpening your critical wit, you’ll need to do some planning. First of all, you’ll naturally need to decide what the subject of your review site is going to be. As we discussed earlier, an excellent place to start when picking a niche is by considering your own interests.

      This will help you produce more authoritative reviews, as you’ll have some pre-existing knowledge to rely on. After all, few would be interested in reading reviews about board games, for example, if the writer clearly had little understanding of or experience playing them. Being passionate about your chosen topic will also make the overall experience much more enjoyable.

      When it comes to finding a niche you can fill, it’s a good idea to do some market research. You can start by looking at other review sites and investigating forums related to your subject matter, to see what users think of your competitors. This might give you some ideas about how you could tailor your reviews to better serve your target audience. If you can find an angle that no other site is using, you’ll have a better chance of success.

      You should also decide what methods you want to use to monetize your site. This could involve featuring paid advertisements, such as banners, or including affiliate links alongside your reviews. You could also offer exclusive content to those who sign up for a paid subscription.

      At last, you’ll need to consider the more practical aspects. What will your website look like and who will actually be writing the reviews? If you’re starting small, you might want to begin with a simple blog and yourself as the sole author. However, you can also go big right away with a more intricate structure and even hire a whole team of writers.

      Naturally, the scope of your site will depend largely on your goals and budget. It’s often best to start smaller and then expand your site over time, as this will minimize risks and enable you to grow organically as you receive more traffic. This is similar to creating a Minimum Viable Product (MVP), where you start with a bare-bones approach, focusing on a simple layout and high-quality content, and then scale it up gradually.

      How to Start a Review Site With WordPress (In 5 Steps)

      Once you have a plan and a niche in place, you’re ready to get busy with the fun part — actually creating your review site. To help you along, we’re going to walk you through the main steps involved.

      We’ll be using WordPress, so you’ll first need to install and set up a website, which should only take you a few minutes. After that, you’re ready to get to work!

      Step 1: Pick a Name and Host

      First and foremost, you’ll need to think up a name for your site. This part can be a lot of fun, as you get to be creative in order to find a name that suits your site’s intended tone and branding. While you can pick pretty much any name you want, there are some considerations to keep in mind. For example, your site’s name should be:

      • Memorable. It’s important that your name sticks in people’s memories. Making it short and punchy is a smart way to ensure this.
      • Unique. Naturally, you don’t want your site to get confused with anyone else’s. Once you have a list of possible names, simply use a search engine like Google to ensure that no other site is already using it (or a name that’s too similar).
      • On-brand. Make sure that your site’s name matches its identity and target audience. For example, a ‘quirky,’ modern name might not be ideal if you’re aiming for a straightlaced professional market. However, that type of name could be perfectly suited to a site with a more casual approach.

      It’s also essential that you can purchase a domain that matches your site’s name. As such, it’s a good idea to use a domain checker, to see if your top choices are available at a reasonable price.

      If you’re still struggling to think of a decent name and matching domain, there are also name generation tools that can help you brainstorm ideas. DomainWheel, for example, will create suggested names based on a specific term or category.

      A search on DomainWheel.com.Once you have your domain in place, you’ll also need to consider hosting. Since you’re likely expecting a decent amount of traffic, you’ll need a hosting plan that can ensure top-notch performance at all times. This will also ensure scalability as your site grows over time. Our recommendation would be to go with a WordPress-specific hosting plan, as this will make setting up and maintaining your site simple.

      Step 2: Install a Suitable Theme

      Next, you’ll want to consider your site’s appearance. Fortunately, there are plenty of WordPress themes tailored specifically to review sites. While you don’t need to use a dedicated review theme, it can offer you several unique benefits.

      First of all, a review theme will be able to accommodate the layout and style of a review site easily. Many review themes also include specific functionality that can come in handy, like styles for applying scores or the ability to create lists of the reviews with the highest ratings.

      One example is the InReview theme.

      Example of a page using the InReview theme.

      This theme enables you to showcase your reviews alongside your final scores. You can also display ratings from your users to give readers a more rounded overview of each item.

      If you want something more stylish and with a magazine-like feel, there are also lots of suitable options. One of our favorites is the GoodLife theme.

      Example of a review using the GoodLife theme.

      With this theme, you can style your reviews using several different templates. Its goal is to help you create a modern, clean look, where the content is the central focus.

      Ultimately, the theme you decide to use depends mainly on your goals and target market. As such, it’s worth spending some time to find the perfect option.

      Step 3: Enhance Your Site With Review Plugins

      With the right theme installed, your site might already be equipped with some useful review features. However, you can improve its functionality even further by adding some select plugins. In this section, we’re going to introduce a few of the best plugins to enhance your review site.

      Let’s start with WP Product Review, which enables you to design a scoring interface.

      Alt text: The WP Product Review plugin.

      Once you’ve installed this plugin, you can specify if a post is a review. Then you can assign scores to the post and designate parameters, such as Pros and Cons. Plus, everything can be fully customized with new colors and icons.

      In addition to displaying scoring information on your site, you can also highlight it right in Google’s search results. To do that, you can use All In One Schema Rich Snippets.

      The All In One Schema Rich Snippets plugin.

      This tool will add schema markup to your pages, which will display information such as scores when your posts appear in search results. This can help your content stand out more, which is crucial for encouraging organic traffic.

      Finally, you may want to give your users the chance to submit their own reviews and scores. One plugin that lets you do this is Ultimate Reviews.

      The Ultimate Reviews plugin.

      This plugin lets you support user reviews, even enabling you to tailor precisely what information they can include. You could implement a simple score-only system, for example, or provide the tools needed to write long-form reviews.

      Naturally, this is only scratching the surface of the plugins that are available. For instance, you can also use a plugin like Reviewer WordPress to create a review comparison table, and Taqyeem to implement a summary box for your reviews. The possibilities are just about endless.

      Step 4: Start Writing Reviews

      Finally, the moment has come to actually start writing your reviews. Of course, we can’t help you much with this part, as you’ll need to rely on your own writing skills and critical thinking. However, to get started you may want to check out our blogging checklist and take a look at our expert blogging tips.

      We also recommend that you create a style guide. This will help you write consistent reviews that follow a specific set of standards, especially when it comes to the style of writing and the criteria you’ll use to rate products.

      A style guide is particularly helpful when you’re bringing in other writers, as it ensures that all posts follow a consistent ruleset. However, you’ll also want each writer’s personal style shine through, so try not to get too specific to avoid stifling their unique voices.

      It’s also a smart idea to have a handful of reviews ready before you launch the site. This will ensure that your site doesn’t feel empty when it goes live. You want to give your new visitors a good first impression, after all, and provide them with a reason to stay around longer.

      Step 5: Share Your Reviews

      Once your site has gone live, you’ll need to make the world aware of its existence. As such, you’ll want to start marketing your website right away, to ensure that you get a steady stream of traffic right out of the gate.

      Naturally, you’ll want to spend some time on SEO and make sure your site has a presence on social media. Share your reviews frequently and encourage your readers to do the same. The more your content is spread around, the more traffic you should see as a result.

      You might also consider submitting your site to a review aggregator. As we mentioned earlier, these sites collect reviews from multiple places to calculate average scores. Being featured on this type of site can help your reviews become more visible and reach new readers.

      Rave Reviews

      If you want to build an audience and make money online, while working with a subject matter that interests you, a review site is an ideal vehicle. By creating well-written and engaging reviews, you can provide valuable information to your readers, and create ample opportunity to monetize your work.

      Do you have any questions about starting your own review site with WordPress? Join the DreamHost Community today and ask away!



      Source link

      Automate Static Site Deployments with Salt, Git, and Webhooks


      Updated by Linode Contributed by Nathan Melehan

      Use promo code DOCS10 for $10 credit on a new account.

      This guide will walk through the deployment of a static site using SaltStack, which is a flexible configuration management system. The configuration files created for Salt will be version controlled using Git. Updates to your static site’s code will be automatically communicated to the production system using webhooks, an event notification system for the web.

      Setting up these mechanisms offers an array of benefits:

      • Using webhooks will keep your production website in sync with your development without any actions needed on your part.

      • Using Salt provides an extensible, reliable way to alter your production systems and minimize human error.

      • Version controlling your configuration management helps you track or revert the changes you’ve made to your systems and collaborate with others on your deployments.

      Development and Deployment Workflow

      The static site generator used in this guide is Hugo, a fast framework written in Go. Static site generators compile markdown or other content files into HTML files. This guide can easily be adapted to other frameworks.

      Two Git repositories will be created: one will track changes to the Hugo site, and the other will track Salt’s configuration files. Remote repositories will be created for both on GitHub.

      Two Linodes will be created: one will act as the Salt master, and the other as the Salt minion. This guide was tested under Debian 9, but the instructions may work with other distributions as well. The Salt minion will run the production webserver which serves the Hugo site, and the master will configure the minion’s software. The minion will also run a webhook server which will receive code update notifications from GitHub.

      It is possible to run Salt in a masterless mode, but using a Salt master will make it easier to expand on your deployment in the future.

      Note

      The workflow described in this guide is similar to how Linode’s own Guides & Tutorials website is developed and deployed.

      Before You Begin

      Set Up the Development Environment

      Development of your Hugo site and your Salt formula will take place on your personal computer. Some software will need to be installed on your computer first:

      1. Install Git using one of the methods in Linode’s guide. If you have a Mac, use the Homebrew method, as it will also be used to install Hugo.

      2. Install Hugo. The Hugo documentation has a full list of installation methods, and instructions for some popular platforms are as follows:

        • Debian/Ubuntu:

          sudo apt-get install hugo
          
        • Fedora, Red Hat and CentOS:

          sudo dnf install hugo
          
        • Mac, using Homebrew:

          brew install hugo
          
        • Windows, using Chocolatey

          choco install hugo -confirm
          

      Deploy the Linodes

      1. Follow the Getting Started guide and deploy two Linodes running Debian 9.

      2. In the settings tab of your Linodes’ dashboards, label one of the Linodes as salt-master and the other as salt-minion. This is not required, but it will help keep track of which Linode serves which purpose.

      3. Complete the Securing Your Server guide on each Linode to create a limited Linux user account with sudo privileges, harden SSH access, and remove unnecessary network services.

        Note

        This guide is written for a non-root user. Commands that require elevated privileges are prefixed with sudo. If you’re not familiar with the sudo command, visit our Users and Groups guide.

        All configuration files should be edited with elevated privileges. Remember to include sudo before running your text editor.

      4. Configure DNS for your site by adding a domain zone and setting up reverse DNS on your Salt minion’s IP address.

      Set Up the Salt Master and Salt Minion

      Before you can start setting up the Salt formulas for the minion, you first need to install the Salt software on the master and minion and set up communication between them.

      1. Log into the Salt master Linode via SSH and run the Salt installation bootstrap script:

        wget -O bootstrap-salt.sh https://bootstrap.saltstack.com
        sudo sh bootstrap-salt.sh -M -N
        

        Note

        The -M option tells the script to install the Salt master software, and the -N option tells the script to not install the minion software.

      2. Log into the Salt minion Linode via SSH and set the hostname. This guide uses hugo-webserver as the example hostname:

        sudo hostnamectl set-hostname hugo-webserver
        

        Note

        This step needs to be completed before installing Salt on the minion, as Salt will use your hostname to generate the minion’s Salt ID.

      3. Edit the minion’s /etc/hosts file and append a new line for your hostname after the localhost line; replace 192.0.2.3 with your minion’s public IP address:

        /etc/hosts
        1
        2
        3
        
        127.0.0.1       localhost
        192.0.2.3       hugo-webserver
        # [...]
      4. Run the bootstrap script on the minion:

        wget -O bootstrap-salt.sh https://bootstrap.saltstack.com
        sudo sh bootstrap-salt.sh
        
      5. Edit /etc/salt/minion on the Salt minion. Uncomment the line that begins with #master: and enter your Salt master’s IP after the colon (in place of 192.0.2.2):

        /etc/salt/minion
        1
        2
        3
        
        # [...]
        master: 192.0.2.2
        # [...]

        Note

        Linode does not charge for traffic within a datacenter across private IP addresses. If your Salt master and minion are in the same datacenter, and both have a private IP addresses, you can use your Salt master’s private IP address in this step to avoid incurring data traffic charges.

      6. Restart Salt on the minion:

        sudo systemctl restart salt-minion
        

      Salt Minion Authentication

      The minion should now be able to find the master, but it has not yet been authenticated to communicate with the master. Salt uses public-private keypairs to authenticate minions to masters.

      1. On the master, list fingerprints for all the master’s local keys, accepted minion keys, and unaccepted keys:

        sudo salt-key --finger-all
        

        The output should resemble:

          
        Local Keys:
        master.pem:  fe:1f:e8:3d:26:83:1c:...
        master.pub:  2b:93:72:b3:3a:ae:cb:...
        Unaccepted Keys:
        hugo-webserver:  29:d8:f3:ed:91:9b:51:...
        
        

        Note

        The example fingerprints in this section have been truncated for brevity.

      2. Copy the fingerprint for master.pub from the output of salt-key --finger-all. On your Salt minion, open /etc/salt/minion in a text editor. Uncomment the line that begins with #master_finger: and enter the value for your master.pub after the colon in single-quotes:

        /etc/salt/minion
        1
        2
        3
        
        # [...]
        master_finger: '0f:d6:5f:5e:f3:4f:d3:...'
        # [...]
      3. Restart Salt on the minion:

        sudo systemctl restart salt-minion
        
      4. View the minion’s local key fingerprint:

        sudo salt-call key.finger --local
        
          
        local:
            29:d8:f3:ed:91:9b:51:...
        
        

        Compare the output’s listed fingerprint to the fingerprints listed by the Salt master for any Unaccepted Keys. This is the output of salt-key --finger-all run on the master in the beginning of this section.

      5. After verifying, that the minion’s fingerprint is the same as the fingerprint detected by the Salt master, run the following command on the master to accept the minion’s key:

        sudo salt-key -a hugo-webserver
        
      6. From the master, verify that the minion is running:

        sudo salt-run manage.up
        

        You can also run a Salt test ping from the master to the minion:

        sudo salt 'hugo-webserver' test.ping
        
          
        hugo-webserver:
            True
        
        

      Initialize the Salt Minion’s Formula

      The Salt minion is ready to be configured by the master. These configurations will be written in a Salt formula which will be hosted on GitHub.

      1. On your computer, create a new directory to hold your minion’s formula and change to that directory:

        mkdir hugo-webserver-salt-formula
        cd hugo-webserver-salt-formula
        
      2. Inside the formula directory, create a new hugo directory to hold your webserver’s configuration:

        mkdir hugo
        
      3. Inside the hugo directory, create a new install.sls file:

        hugo-webserver-salt-formula/hugo/install.sls
        1
        2
        3
        
        nginx_pkg:
          pkg.installed:
            - name: nginx

        Note

        Salt configurations are declared in YAML– a markup language that incorporates whitespace/indentation in its syntax. Be sure to use the same indentation as the snippets presented in this guide.

        A .sls file is a SaLt State file. Salt states describe the state a minion should be in after the state is applied to it: e.g., all the software that should be installed, all the services that should be run, and so on.

        The above snippet says that a package with name nginx (i.e. the NGINX web server) should be installed via the distribution’s package manager. Salt knows how to negotiate software installation via the built-in package manager for various distributions. Salt also knows how to install software via NPM and other package managers.

        The string nginx_pkg is the ID for the state component, pkg is the name of the Salt module used, and pkg.installed is referred to as a function declaration. The component ID is arbitrary, so you can name it however you prefer.

        Note

        If you were to name the ID to be the same as the relevant installed package, then you do not need to specify the - name option, as it will be inferred from the ID. For example, this snippet also installs NGINX:

        hugo-webserver-salt-formula/hugo/install.sls

        The same name/ID convention is true for other Salt modules.

      4. Inside the hugo directory, create a new service.sls file:

        hugo-webserver-salt-formula/hugo/service.sls
        1
        2
        3
        4
        5
        6
        
        nginx_service:
          service.running:
            - name: nginx
            - enable: True
            - require:
              - pkg: nginx_pkg

        This state says that the nginx service should be immediately run and be enabled to run at boot. For a Debian 9 system, Salt will set the appropriate systemd configurations to enable the service. Salt also supports other init systems.

        The require lines specify that this state component should not be applied until after the nginx_pkg component has been applied.

        Note

        Unless specified by a require declaration, Salt makes no guarantees about the order that different components are applied. The order that components are listed in a state file does not necessarily correspond with the order that they are applied.

      5. Inside the hugo directory, create a new init.sls file with the following contents:

        hugo-webserver-salt-formula/hugo/init.sls
        1
        2
        3
        
        include:
          - hugo.install
          - hugo.service

        Using the include declaration in this way simply concatenates the install.sls and service.sls files into a single combined state file.

        Right now, these state files only install and enable NGINX. More functionality will be enabled later in this guide.

        The install and service states will not be applied to the minion on their own–instead, only the combined init state will ever be applied. In Salt, when a file named init.sls exists inside a directory, Salt will refer to that particular state by the name of the directory it belongs to (i.e. hugo in our example).

        Note

        The organization of the state files used here is not mandated by Salt. Salt does not place restrictions on how you organize your states. This specific structure is presented as an example of a best practice.

      Push the Salt Formula to GitHub

      1. Inside your hugo-webserver-salt-formula directory on your computer, initialize a new Git repository:

        cd ~/hugo-webserver-salt-formula
        git init
        
      2. Stage the files you just created:

        git add .
        
      3. Review the staged files:

        git status
        
          
        On branch master
        No commits yet
        Changes to be committed:
          (use "git rm --cached ..." to unstage)
        
          new file:   hugo/init.sls
          new file:   hugo/install.sls
          new file:   hugo/service.sls
        
        
      4. Commit the files:

        git commit -m "Initial commit"
        
      5. Log into the GitHub website in your browser and navigate to the Create a New Repository page.

      6. Create a new public repository with the name hugo-webserver-salt-formula:

        GitHub New Repository - Add New Salt Formula Repo

      7. Copy the HTTPS URL for your new repository:

        GitHub New Repository - New Salt Formula Repo

      8. In your local Salt formula repository, add the GitHub repository as the origin remote and push your new files to it. Replace github-username with your GitHub user:

        git remote add origin https://github.com/github-username/hugo-webserver-salt-formula.git
        git push -u origin master
        

        Note

        If you haven’t pushed anything else to your GitHub account from the command line before, you may be prompted to authenticate with GitHub. If you have two-factor authentication enabled for your account, you will need to create and use a personal access token.
      9. If you navigate back to your hugo-webserver-salt-formula repository on GitHub and refresh the page, you should now see your new files.

      Enable GitFS on the Salt Master

      Update your Salt master to serve the new formula from GitHub:

      1. Salt requires that you install a Python interface to Git to use GitFS. On the Salt master Linode:

        sudo apt-get install python-git
        
      2. Open /etc/salt/master in a text editor. Uncomment the fileserver_backend declaration and enter roots and gitfs in the declaration list:

        /etc/salt/master
        1
        2
        3
        
        fileserver_backend:
          - roots
          - gitfs

        roots refers to Salt files stored on the master’s filesystem. While the Hugo webserver Salt formula is stored on GitHub, the Salt Top file will be stored on the master. The Top file is how Salt maps states to the minions they will be applied to.

      3. In the same file, uncomment the gitfs_remotes declaration and enter your Salt formula’s repository URL:

        /etc/salt/master
        1
        2
        
        gitfs_remotes:
          - https://github.com/your_github_user/hugo-webserver-salt-formula.git
      4. Uncomment the gitfs_provider declaration and set its value to gitpython:

        /etc/salt/master
        1
        
        gitfs_provider: gitpython

      Apply the Formula’s State to the Minion

      1. In /etc/salt/master, uncomment the file_roots declaration and set the following values:

        /etc/salt/master
        1
        2
        3
        
        file_roots:
          base:
            - /srv/salt/

        file_roots specifies where state files are kept on the Master’s filesystem. This is referenced when - roots is declared in the fileserver_backend section. base refers to a Salt environment, which is a tree of state files that can be applied to minions. This guide will only use the base environment, but other environments could be created for development, QA, and so on.

      2. Restart Salt on the master to enable the changes in /etc/salt/master:

        sudo systemctl restart salt-master
        
      3. Create the /srv/salt directory on the Salt master:

        sudo mkdir /srv/salt
        
      4. Create a new top.sls file in /srv/salt:

        /srv/salt/top.sls
        1
        2
        3
        
        base:
          'hugo-webserver':
            - hugo

        This is Salt’s Top file, and the snippet declares that the hugo-webserver minion should receive the init.sls state from the hugo directory (from your GitHub-hosted Salt formula).

      5. Tell Salt to apply states from the Top file to the minion:

        sudo salt 'hugo-webserver' state.apply
        

        Salt as refers to this command as a highstate. Running a highstate can take a bit of time to complete, and the output of the command will describe what actions were taken on the minion. The output will also show if any actions failed.

        Note

        If you see an error similar to:

          
        No matching sls found for 'hugo' in env 'base'
        
        

        Try running this command to manually fetch the Salt formula from GitHub, then run the state.apply command again:

        sudo salt-run fileserver.update
        

        Salt’s GitFS fetches files from remotes periodically, and this period can be configured.

      6. If you visit your domain name in a web browser, you should now see NGINX’s default test page served by the Salt minion.

      Initialize the Hugo Site

      1. On your computer, create a new Hugo site. Make sure you are not running this command in your hugo-webserver-salt-formula directory:

        hugo new site example-hugo-site
        
      2. Navigate to the new Hugo site directory and initialize a Git repository:

        cd example-hugo-site
        git init
        
      3. Install a theme into the themes/ directory. This guide uses the Cactus theme:

        git submodule add https://github.com/digitalcraftsman/hugo-cactus-theme.git themes/hugo-cactus-theme
        
      4. The theme comes with some example content. Copy it into the root of your site so that it can be viewed:

        cp -r themes/hugo-cactus-theme/exampleSite/ .
        
      5. Edit the baseurl, themesDir, and name options in config.toml as follows; replace example.com with your own domain and Your Name with your own name:

        example-hugo-site/config.toml
        1
        2
        3
        4
        5
        6
        
        # [...]
        baseURL = "http://example.com"
        # [...]
        themesDir = "themes"
        # [...]
          name = "Your Name"
      6. Run the Hugo development server on your computer:

        hugo server
        

        The output from this command will end with a line like:

          
        Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
        
        
      7. If you view the URL from this output in a browser, you can see your new Hugo site:

        New Hugo Site - Development Server

      8. Enter CTRL-C in the terminal session on your computer to stop the Hugo development server. Open the .gitignore file and make sure public/ is listed. The default .gitignore from the Cactus theme should look like:

        example-hugo-site/config.toml

        The public directory is the result of Hugo compiling the Markdown content files into HTML. These files can be regenerated by anyone who downloads your site code, so they won’t be checked into version control.

      Push the Hugo Site to GitHub

      1. In the Hugo site directory, commit the new site files:

        git add .
        git commit -m "Initial commit"
        
      2. Create a new public repository on GitHub named example-hugo-site and copy the repository’s HTTPS URL.

      3. In the site directory, add the GitHub repository as the origin remote and push your new files to it; replace github-username with your GitHub user:

        git remote add origin https://github.com/github-username/example-hugo-site.git
        git push -u origin master
        

      Deploy the Hugo Site

      The Salt minion’s formula needs to be updated in order to serve the Hugo site. Specifically, the formula will need to have states which:

      • Install Git and clone the Hugo site repository from GitHub.

      • Install Hugo and build the HTML files from the markdown content.

      • Update the NGINX configuration to serve the built site.

      Some of the new state components will refer to data stored in Salt Pillar. Pillar is a Salt system that stores private data and other parameters that you don’t want to list in your formulas. The Pillar data will be kept as a file on the Salt master and not checked into version control.

      Note

      There are methods for securely checking this data into version control or using other backends to host the data, but those strategies are outside the scope of this guide.

      Pillar data is injected into state files with Salt’s Jinja templating feature. State files are first evaluated as Jinja templates and then as YAML afterwards.

      Install Git and Hugo

      In your local Salt formula’s repository, edit the install.sls file to append the git_pkg and hugo_pkg states:

      hugo-webserver-salt-formula/hugo/install.sls
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      # [...]
      
      git_pkg:
        pkg.installed:
          - name: git
      
      hugo_pkg:
        pkg.installed:
          - name: hugo
          - sources:
            - hugo: https://github.com/gohugoio/hugo/releases/download/v{{ pillar['hugo_deployment_data']['hugo_version'] }}/hugo_{{ pillar['hugo_deployment_data']['hugo_version'] }}_Linux-64bit.deb

      The first state component installs Git, and the second component installs Hugo. The second component’s sources declaration specifies that the package should be downloaded from Hugo’s GitHub repository (instead of from the distribution package manager).

      The {{ }} syntax that appears in {{ pillar['hugo_deployment_data']['hugo_version'] }} is a Jinja substitution statement. pillar['hugo_deployment_data']['hugo_version'] returns the value of the hugo_version key from a dictionary named hugo_deployment_data in Pillar. Keeping the Hugo version in Pillar lets you update Hugo without needing to update your formulas.

      Clone the Hugo Site Git Repository

      Create a new config.sls file in your local Salt formula repository’s hugo directory:

      hugo-webserver-salt-formula/hugo/config.sls
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      hugo_group:
        group.present:
          - name: {{ pillar['hugo_deployment_data']['group'] }}
      
      hugo_user:
        user.present:
          - name: {{ pillar['hugo_deployment_data']['user'] }}
          - gid: {{ pillar['hugo_deployment_data']['group'] }}
          - home: {{ pillar['hugo_deployment_data']['home_dir'] }}
          - createhome: True
          - require:
            - group: hugo_group
      
      hugo_site_repo:
        cmd.run:
          - name: git clone --recurse-submodules https://github.com/{{ pillar['hugo_deployment_data']['github_account'] }}/{{ pillar['hugo_deployment_data']['site_repo_name'] }}.git
          - cwd: {{ pillar['hugo_deployment_data']['home_dir'] }}
          - runas: {{ pillar['hugo_deployment_data']['user'] }}
          - creates: {{ pillar['hugo_deployment_data']['home_dir'] }}/{{ pillar['hugo_deployment_data']['site_repo_name'] }}
          - require:
            - pkg: git_pkg
            - user: hugo_user

      The final hugo_site_repo component in this snippet is responsible for cloning the example Hugo site repository from GitHub. This cloned repo is placed in the home directory of a system user that Salt creates in the preceding components. The clone command also recursively downloads the Cactus theme submodule.

      Note

      The - creates declaration tells Salt that running the cmd command module will result in the creation of the file that’s specified. If the state is applied again later, Salt will check if that file already exists. If it exists, Salt will not run the module again.

      The require declarations in each component ensure that:

      • The clone is not run until the system user and home directory have been created, and until the software package for Git has been installed.
      • The user is not created until the group it belongs to is created.

      Instead of hard-coding the parameters for the user, group, home directory, GitHub account, and repository name, these are retrieved from Pillar.

      Configure NGINX

      1. Append the following states to your config.sls:

        hugo-webserver-salt-formula/hugo/config.sls
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        
        nginx_default:
          file.absent:
            - name: '/etc/nginx/sites-enabled/default'
            - require:
              - pkg: nginx_pkg
        
        nginx_config:
          file.managed:
            - name: /etc/nginx/sites-available/hugo_site
            - source: salt://hugo/files/hugo_site
            - user: root
            - group: root
            - mode: 0644
            - template: jinja
            - require:
              - pkg: nginx_pkg
        
        nginx_symlink:
          file.symlink:
            - name: /etc/nginx/sites-enabled/hugo_site
            - target: /etc/nginx/sites-available/hugo_site
            - user: root
            - group: root
            - require:
              - file: nginx_config
        
        nginx_document_root:
          file.directory:
            - name: {{ pillar['hugo_deployment_data']['nginx_document_root'] }}/{{ pillar['hugo_deployment_data']['site_repo_name'] }}
            - user: {{ pillar['hugo_deployment_data']['user'] }}
            - group: {{ pillar['hugo_deployment_data']['group'] }}
            - dir_mode: 0755
            - require:
              - user: hugo_user
        • The nginx_default component removes the symlink in sites-enabled for the default NGINX config, which disables that configuration.
        • nginx_config and nginx_symlink then create a new configuration file in sites-available and a symlink to it in sites-enabled.
        • The nginx_document_root component creates the directory that NGINX will serve your Hugo site files from (when filled in with Pillar data, this will directory will look like /var/www/example-hugo-site).
      2. The - source: salt://hugo/files/hugo_site declaration in nginx_config refers to an NGINX configuration file that doesn’t exist in your repository yet. Create the files/ directory:

        cd ~/hugo-webserver-salt-formula/hugo
        mkdir files
        
      3. Create the hugo_site file inside files/:

        hugo-webserver-salt-formula/hugo/files/hugo_site
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        
        server {
            listen 80;
            listen [::]:80;
            server_name {{ pillar['hugo_deployment_data']['domain_name'] }};
        
            root {{ pillar['hugo_deployment_data']['nginx_document_root'] }}/{{ pillar['hugo_deployment_data']['site_repo_name'] }};
        
            index index.html index.htm index.nginx-debian.html;
        
            location / {
                try_files $uri $uri/ = /404.html;
            }
        }

        The nginx_config component that manages this file also listed the - template: jinja declaration, so the source file is interpreted as a Jinja template. The source file is able to substitute values from Pillar using the Jinja substitution syntax.

      4. Replace the content of your service.sls with this snippet:

        hugo-webserver-salt-formula/hugo/service.sls
        1
        2
        3
        4
        5
        6
        7
        8
        
        nginx_service:
          service.running:
            - name: nginx
            - enable: True
            - require:
              - file: nginx_symlink
            - watch:
              - file: nginx_config

        The nginx_service component now requires nginx_symlink instead of nginx_pkg. Without this change, the service may be enabled and run before the new NGINX configuration is set up. The - watch declaration also instructs NGINX to restart whenever a change to nginx_config is made.

      Build Hugo

      1. Append a build_script state to config.sls:

        hugo-webserver-salt-formula/hugo/config.sls
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        
        build_script:
          file.managed:
            - name: {{ pillar['hugo_deployment_data']['home_dir'] }}/deploy.sh
            - source: salt://hugo/files/deploy.sh
            - user: {{ pillar['hugo_deployment_data']['user'] }}
            - group: {{ pillar['hugo_deployment_data']['group'] }}
            - mode: 0755
            - template: jinja
            - require:
              - user: hugo_user
          cmd.run:
            - name: ./deploy.sh
            - cwd: {{ pillar['hugo_deployment_data']['home_dir'] }}
            - runas: {{ pillar['hugo_deployment_data']['user'] }}
            - creates: {{ pillar['hugo_deployment_data']['nginx_document_root'] }}/{{ pillar['hugo_deployment_data']['site_repo_name'] }}/index.html
            - require:
              - file: build_script
              - cmd: hugo_site_repo
              - file: nginx_document_root

        This state uses more than one module. The first module will download the deploy.sh file from the salt master and place it on the minion. This script will be responsible for compiling your Hugo site files. The second module then calls that script. The first module is listed as a requirement of the second module, along with the Git clone command, and the creation of the document root folder.

        Note

        The - creates option in the second module ensures that Salt doesn’t rebuild Hugo if the state is re-applied to the minion.

      2. Create the deploy.sh script in files/:

        hugo-webserver-salt-formula/hugo/files/deploy.sh
        1
        2
        3
        4
        
        #!/bin/bash
        
        cd {{ pillar['hugo_deployment_data']['site_repo_name'] }}
        hugo --destination={{ pillar['hugo_deployment_data']['nginx_document_root'] }}/{{ pillar['hugo_deployment_data']['site_repo_name'] }}

        Hugo’s build function is called with NGINX’s document root as the destination for the built files.

      3. Update init.sls to include the new config.sls file:

        hugo-webserver-salt-formula/hugo/init.sls
        1
        2
        3
        4
        
        include:
          - hugo.install
          - hugo.config
          - hugo.service

      Push the Salt Formula Updates to GitHub

      Your state files should now have these contents: init.sls, install.sls, config.sls, service.sls.

      The files present in your Salt formula repository should be:

        
      hugo
      ├── config.sls
      ├── files
      │   ├── deploy.sh
      │   └── hugo_site
      ├── init.sls
      ├── install.sls
      └── service.sls
      
      
      1. Stage all the changes you made to your local Salt formula files in the previous steps and then commit the changes:

        cd ~/hugo-webserver-salt-formula
        git add .
        git commit -m "Deploy the Hugo site"
        
      2. Push the commit to your GitHub repository:

        git push origin master
        

      Create the Salt Pillar File

      1. Open /etc/salt/master on the Salt master in a text editor. Uncomment the pillar_roots section:

        /etc/salt/master
        1
        2
        3
        
        pillar_roots:
          base:
            - /srv/pillar

        pillar_roots performs an analogous function to file_roots: it specifies where Pillar data is stored on the master’s filesystem.

      2. Restart Salt on the master to enable the changes in /etc/salt/master:

        sudo systemctl restart salt-master
        
      3. Create the /srv/pillar directory on the Salt master:

        sudo mkdir /srv/pillar
        
      4. Create an example-hugo-site.sls file in /srv/pillar to contain the Pillar data for the minion. This file uses the same YAML syntax as other state files. Replace the values for github_account and domain_name with your GitHub account and your site’s domain name:

        /srv/pillar/example-hugo-site.sls
        1
        2
        3
        4
        5
        6
        7
        8
        9
        
        hugo_deployment_data:
          hugo_version: 0.49
          group: hugo
          user: hugo
          home_dir: /home/hugo
          github_account: your_github_user
          site_repo_name: example-hugo-site
          nginx_document_root: /var/www
          domain_name: yourdomain.com
      5. Create a top.sls file in /srv/pillar. Similar to the Top file in your state tree, the Pillar’s Top file maps Pillar data to minions:

        /srv/pillar/top.sls
        1
        2
        3
        
        base:
          'hugo-webserver':
            - example-hugo-site

      Apply State Updates to the Minion

      On the Salt master, apply the new states to all minions:

      sudo salt '*' state.apply
      

      Note

      In this guide there is only one minion, but Salt can use shell-style globbing and regular expressions to match against minion IDs when you have more than one. For example, this command would run a highstate on all minions whose IDs begin with hugo:

      sudo salt 'hugo*' state.apply
      

      If no changes are made, try manually fetching the Salt formula updates from GitHub and then run the state.apply command again:

      sudo salt-run fileserver.update
      

      When the operation finishes, your Hugo site should now be visible at your domain.

      Deploy Site Updates with Webhooks

      Your site is now deployed to production, but there is no automatic mechanism in place yet for updating the production server when you update your Hugo site’s content. To update the production server, your minion will need to:

      1. Pull the latest changes pushed to the master branch of your Hugo site repository on GitHub.

      2. Run the Hugo build process with the new content.

      The deploy.sh script can be altered to pull changes from GitHub. These script changes will be made in the Salt formula repository. Then, we’ll set up webhooks to notify the Salt minion that updates have been made to the Hugo site.

      Webhooks are HTTP POST requests specifically designed and sent by systems to communicate some kind of significant event. A webhook server listens for these requests and then takes some action when it receives one. For example, a GitHub repository can be configured to send webhook notifications whenever a push is made to the repository. This is the kind of notification we’ll configure, and the Salt minion will run a webhook server to receive them. Other event notifications can also be set up on GitHub.

      Set Up a Webhook Server on the Salt Minion

      1. In your local Salt formula repository, append a new webhook_pkg state to your install.sls that installs the webhook server package by adnanh:

        hugo-webserver-salt-formula/hugo/install.sls
        1
        2
        3
        
        webhook_pkg:
          pkg.installed:
            - name: webhook

        Note

        The webhook server written in Go by adnanh is a popular implementation of the concept, but it’s possible to write other HTTP servers that parse webhook payloads.

      2. Append two new components to your config.sls:

        hugo-webserver-salt-formula/hugo/config.sls
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        
        webhook_systemd_unit:
          file.managed:
            - name: '/etc/systemd/system/webhook.service'
            - source: salt://hugo/files/webhook.service
            - user: root
            - group: root
            - mode: 0644
            - template: jinja
            - require:
              - pkg: webhook_pkg
          module.run:
            - name: service.systemctl_reload
            - onchanges:
              - file: webhook_systemd_unit
        
        webhook_config:
          file.managed:
            - name: '/etc/webhook.conf'
            - source: salt://hugo/files/webhook.conf
            - user: root
            - group: {{ pillar['hugo_deployment_data']['group'] }}
            - mode: 0640
            - template: jinja
            - require:
              - pkg: webhook_pkg
              - group: hugo_group

        The first state creates a systemd unit file for the webhook service. The second state creates a webhook configuration. The webhook server reads the configuration and generates a webhook URL from it.

      3. Create a webhook.service file in your repository’s files/ directory:

        hugo-webserver-salt-formula/hugo/files/webhook.service
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        [Unit]
        Description=Small server for creating HTTP endpoints (hooks)
        Documentation=https://github.com/adnanh/webhook/
        
        [Service]
        User={{ pillar['hugo_deployment_data']['user'] }}
        ExecStart=/usr/bin/webhook -nopanic -hooks /etc/webhook.conf
        
        [Install]
        WantedBy=multi-user.target
      4. Create a webhook.conf file in your repository’s files/ directory:

        hugo-webserver-salt-formula/hugo/files/webhook.conf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        
        [
          {
            "id": "github_push",
            "execute-command": "{{ pillar['hugo_deployment_data']['home_dir'] }}/deploy.sh",
            "command-working-directory": "{{ pillar['hugo_deployment_data']['home_dir'] }}",
            "trigger-rule":
            {
              "and":
              [
                {
                  "match":
                  {
                    "type": "payload-hash-sha1",
                    "secret": "{{ pillar['hugo_deployment_data']['webhook_secret'] }}",
                    "parameter":
                    {
                      "source": "header",
                      "name": "X-Hub-Signature"
                    }
                  }
                },
                {
                  "match":
                  {
                    "type": "value",
                    "value": "refs/heads/master",
                    "parameter":
                    {
                      "source": "payload",
                      "name": "ref"
                    }
                  }
                }
              ]
            }
          }
        ]

        This configuration sets up a URL named http://example.com:9000/hooks/github_push, where the last component of the URL is derived from the value of the configuration’s id.

        Note

        The webhook server runs on port 9000 and places your webhooks inside a hooks/ directory by default.

        When a POST request is sent to the URL:

        • The webhook server checks if the header and payload data from the request satisfies the rules in the trigger-rule dictionary, which are:

          • That the SHA1 hash of the server’s webhook secret matches the secret in the request headers. This prevents people who don’t know your webhook secret from triggering the webhook’s action.
          • The ref parameter in the payload matches refs/heads/master. This ensures that only pushes to the master branch trigger the action.
        • If the rules are satisfied, then the command listed in execute-command is run, which is the deploy.sh script.

        Note

        Further documentation on the webhook configuration options can be reviewed on the project’s GitHub repository.
      5. Append a new webhook_service state to your service.sls that enables and starts the webhook server:

        hugo-webserver-salt-formula/hugo/service.sls
        1
        2
        3
        4
        5
        6
        7
        
        webhook_service:
          service.running:
            - name: webhook
            - enable: True
            - watch:
              - file: webhook_config
              - module: webhook_systemd_unit
      6. Update the deploy.sh script so that it pulls changes from master before building the site:

        hugo-webserver-salt-formula/hugo/files/deploy.sh
        1
        2
        3
        4
        5
        
        #!/bin/bash
        
        cd {{ pillar['hugo_deployment_data']['site_repo_name'] }}
        git pull origin master
        hugo --destination={{ pillar['hugo_deployment_data']['nginx_document_root'] }}//{{ pillar['hugo_deployment_data']['site_repo_name'] }}
      7. Your state files should now have these contents: init.sls (unchanged), install.sls, config.sls, service.sls. Save the changes made to your Salt files, then commit and push them to GitHub:

        cd ~/hugo-webserver-salt-formula
        git add .
        git commit -m "Webhook server states"
        git push origin master
        
      8. On the Salt master, add a webhook_secret to the example-hugo-site.sls Pillar. Your secret should be a complex, random alphanumeric string.

        /srv/pillar/example-hugo-site.sls
        1
        2
        3
        
        hugo_deployment_data:
          # [...]
          webhook_secret: your_webhook_secret
      9. From the Salt master, apply the formula updates to the minion:

        sudo salt-run fileserver.update
        sudo salt 'hugo-webserver' state.apply
        
      10. Your webhook server should now be running on the minion. If you run a curl against it, you should see:

        curl http://example.com:9000/hooks/github_push
        
          
        Hook rules were not satisfied.⏎
        
        

      Configure a Webhook on GitHub

      1. Visit your example Hugo site repository on GitHub and navigate to the Webhooks section of the Settings tab. Click on the Add webhook button:

        GitHub - Add Webhook Button

      2. Fill in the form:

        • Enter http://example.com:9000/hooks/github_push for the payload URL (substitute example.com for your own domain).

        • Select application/json for the content type.

        • Paste in the webhook secret that you previously added to Salt Pillar.

        The webhook is configured to notify on push events by default. Keep this option selected.

        GitHub - New Webhook Configuration

      3. Click the green Add webhook button to complete the setup.

      Update the Hugo Site

      1. In your local Hugo site repository, create a new post using Hugo’s archetypes feature:

        hugo new post/test-post.md
        
      2. This command creates a new partially filled in markdown document in content/post/. Open this file in your editor, remove the draft: true line from the frontmatter, and add some body text:

        example-hugo-site/content/post/test-post.md
        1
        2
        3
        4
        5
        6
        
        ---
        title: "Test Post"
        date: 2018-10-19T11:39:15-04:00
        ---
        
        Test post body text
      3. If you run hugo server in the repository directory, you can see the new post:

        Hugo Home Page - Test Post

      4. Commit and push the new post to GitHub:

        cd ~/example-hugo-site
        git add .
        git commit -m "Test post"
        git push origin master
        
      5. Visit your domain in your browser; your test post should automatically appear.

        Note

        If your post does not appear, review the Recent Deliveries section at the bottom of your webhook configuration page on GitHub:

        GitHub Webhook - Recent Deliveries

        If you click on a delivery, full information about the request headers and payload and the server response are shown, and these may provide some troubleshooting information. Editing the webhook.service file so that it starts the service in verbose mode may help.

      Next Steps

      The current Salt configuration can be used as a foundation for more complex deployments:

      • Host multiple Hugo sites by updating Pillar with further GitHub repositories.

      • Host different kinds of static sites by changing the Salt formula to support them.

      • Load balance your site by creating more minions and apply the same Pillar data and Salt states to them. Then, set up a NodeBalancer to direct traffic to the minions.

      • Set up a separate development branch and development server with Salt’s environments feature.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Join our Community

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link