Testing Syntax Errors in Apache Config

If you spend any time mucking around config files in Linux you are likely to run into some syntax errors sooner or later. Recently I was setting up cgit on Debian 8 and was banging my head against the wall for a few minutes trying to figure out why apache was so unhappy.

Symptoms

The key issue was when I restarted apache2 like I normally would after adding a new configuration it spat out an angry message at me.

[email protected]:/etc/apache2# sudo service apache2 restart
Job for apache2.service failed. See 'systemctl status apache2.service' and 'journalctl -xn' for details.

Troubleshooting

The first place that I would look is the error logs. However, in this particular case they were not very helpful.

[email protected]:/etc/apache2# tail -f /var/log/apache2/error.log
[Mon May 01 21:00:11.922943 2017] [mpm_prefork:notice] [pid 20454] AH00169: caught SIGTERM, shutting down

Next, I read the error message per the suggestion from the restart command. This was also not very helpful.

[email protected]:/etc/apache2# systemctl status apache2.service
● apache2.service - LSB: Apache2 web server
 Loaded: loaded (/etc/init.d/apache2)
 Drop-In: /lib/systemd/system/apache2.service.d
 └─forking.conf
 Active: failed (Result: exit-code) since Mon 2017-05-01 21:05:58 PDT; 1min 45s ago
 Process: 20746 ExecStop=/etc/init.d/apache2 stop (code=exited, status=0/SUCCESS)
 Process: 20697 ExecReload=/etc/init.d/apache2 reload (code=exited, status=1/FAILURE)
 Process: 20920 ExecStart=/etc/init.d/apache2 start (code=exited, status=1/FAILURE)

May 01 21:05:58 nuc apache2[20920]: Starting web server: apache2 failed!
May 01 21:05:58 nuc apache2[20920]: The apache2 configtest failed. ... (warning).
May 01 21:05:58 nuc apache2[20920]: Output of config test was:
May 01 21:05:58 nuc apache2[20920]: apache2: Syntax error on line 219 of /etc/apache2/apache2.conf: Syntax error on line 22 of /etc/a... section
May 01 21:05:58 nuc apache2[20920]: Action 'configtest' failed.
May 01 21:05:58 nuc apache2[20920]: The Apache error log may have more information.
May 01 21:05:58 nuc systemd[1]: apache2.service: control process exited, code=exited status=1
May 01 21:05:58 nuc systemd[1]: Failed to start LSB: Apache2 web server.
May 01 21:05:58 nuc systemd[1]: Unit apache2.service entered failed state.
Hint: Some lines were ellipsized, use -l to show in full.

Inspecting the error message, we see that it is unhappy with line 219 of the main /etc/apache2/apache2.conf file. Looking at that line we can see that it is simply loading all of the other config files in sites-enabled which means that before it even gets to load my new cgit config file it fails.

Help

So now that we have done some basic troubleshooting. It’s time to dig into the manual for further information. I know that the config file is failing to load, and knowing my fat fingers it is very likely a config error on my part. Before reading 200 pages of documentation on the apache website we should take a look at the built in help to see if we can find something of value.

[email protected]:/etc/apache2# apache2 -help
Usage: apache2 [-D name] [-d directory] [-f file]
 [-C "directive"] [-c "directive"]
 [-k start|restart|graceful|graceful-stop|stop]
 [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]
Options:
 -D name : define a name for use in <IfDefine name> directives
 -d directory : specify an alternate initial ServerRoot
 -f file : specify an alternate ServerConfigFile
 -C "directive" : process directive before reading config files
 -c "directive" : process directive after reading config files
 -e level : show startup errors of level (see LogLevel)
 -E file : log startup errors to file
 -v : show version number
 -V : show compile settings
 -h : list available command line options (this page)
 -l : list compiled in modules
 -L : list available configuration directives
 -t -D DUMP_VHOSTS : show parsed vhost settings
 -t -D DUMP_RUN_CFG : show parsed run settings
 -S : a synonym for -t -D DUMP_VHOSTS -D DUMP_RUN_CFG
 -t -D DUMP_MODULES : show all loaded modules
 -M : a synonym for -t -D DUMP_MODULES
 -t : run syntax check for config files
 -T : start without DocumentRoot(s) check
 -X : debug mode (only one worker, do not detach)

Success! It turns out we can run a linter on a specific config file using the -t flag.

Solution

[email protected]:/etc/apache2# apache2 -t -f sites-available/git.levlaz.org.conf
apache2: Syntax error on line 22 of /etc/apache2/sites-available/git.levlaz.org.conf: </VirtualHost> without matching <VirtualHost> section

Doh! Such a silly mistake with a missing </VirtualHost> closing bracket. Fixing this syntax error resolved the issue.

The main takeaway for me is that the best part about most Linux tools is that they usually give you everything you need in order to succeed. We were able to troubleshoot and resolve this issue without resorting to google and running random commands that stranger posted on the internet 5 years ago.

Change the Default Terminal Editor in Debian

Debian comes with a very handy utility called update-alternatives that helps to set default tools for various tasks.

It is possible for several programs fulfilling the same or similar functions to be installed on a single system at the same time. For example, many systems have several text editors installed at once. This gives choice to the users of a system, allowing each to use a different editor, if desired, but makes it difficult for a program to make a good choice for an editor to invoke if the user has not specified a particular preference.

On Linode, it seems that the default editor is nano, I prefer to use vim for editing git commits, visudo, and other things that use the default editor which is symbolically linked through /usr/bin/editor. The update-alternatives package basically changes the symbolic links for you.

In order to change your default editor, you simply need to run the following command:

sudo update-alternatives --config editor

The output of this command is shown below. You will see a list of all of your editors that you currently have installed and will be asked to make a choice.

There are 3 choices for the alternative editor (providing /usr/bin/editor).

Selection Path Priority Status
------------------------------------------------------------
 0 /bin/nano 40 auto mode
 1 /bin/nano 40 manual mode
 2 /usr/bin/vim.basic 30 manual mode
* 3 /usr/bin/vim.tiny 10 manual mode

Press enter to keep the current choice[*], or type selection number:

Behind the scenes you can see that all this does it updates the symbolic links.

[email protected]:~$ ls -al /usr/bin/editor
lrwxrwxrwx 1 root root 24 Feb 10 20:49 /usr/bin/editor -> /etc/alternatives/editor
[email protected]:~$ ls -al /etc/alternatives/editor
lrwxrwxrwx 1 root root 17 Apr 28 18:56 /etc/alternatives/editor -> /usr/bin/vim.tiny

There are many other things that can be configured this way. For more information reading the man page for update-alternatives is worthwhile.

Don’t forget the -i

I spent way too much time troubleshooting an issue I was having with sed today. This is a common theme sometimes where I spend upwards of an hour debugging something that is ridiculously obvious.

I was trying to replace a string in a file. This is super simple to do with sed.

sed 's/string/replacement_string/` $file_name

The problem is that this command just spits out the result. If you want to actually save your change you must use the -i flag.

sed -i '/s/string/replacement_string/` $file_name

For more information on how to not suck at Linux like Lev please refer to man $command 😉

Serving a Static Home Page in Rails

TIL while working on this issue that if you dump a file into public/index.html then rails will just serve that up for you.

A lot of tutorials out there talk about making a static pages controller, which seems like overkill (unless you have a lot of static pages .. but if thats the case why are you using Rails?)

Do not Install Karma Globally

Wow, I spent so long trying to figure out why the hell karma was not working for me, it turns out its because it was installed globally.

For instance.

In my projects package.json I had:

"scripts": { "test": "karma start karma.conf.js" } ...

When I ran npm test – it told me sh 1: karma not found

Every other possible combination also did the same thing.

i.e.

node_modules/karma/bin/karma 

./node_modules/karma/bin/karma  

node ./node_modules/karma/bin/karma

I could totally execute this myself from the shell, so I had no idea what was wrong. Then I finally stumbled upon this GitHub Issue.

After uninstalling karma globally, npm uninstall -g karma I was able to run npm test without any issues.

I still have no idea why this works or didn’t work. But at this point I just want to go back to writing tests.

Injecting Stuff into your Python Path

Similar to a previous post where I wrote about how to run flask tests without installing your app, another common thing that you might want to be able to do is import your app from some arbitrary script.

This is especially useful when running your app with apache mod_wsgi. This module expects the app to be installed globally or at least in the python path. Unless you install the app in a traditional sense this will not be true.

The solution is just to inject the path prior to running your import statement like this.

sys.path.insert(0, '/var/www/blog')
from blog import app as application

This import will actually work.

Using the Flask CLI

Who knew that flask had a cli? Previously I used to just use manage.py just like Django does it to “do stuff”.

The CLI is great, but again it follows the theme of kind of wanting you to install your flask app. (I really should do this).

So in order to get your app to work you must point the FLASK_APP variable to the actual python file (not your app module). This is true even if you have a true python module.

For instance.

export FLASK_APP = blog/blog.py
flask run

Works, while

export FLASK_APP = blog
flask run 

Does not. Even though blog consists of:

blog/
  __init__.py
  blog.py

Follow Me on Twitter CTA on Ghost and WordPress

The other day I read a post on Hacker News about premature optimization in web application development. This was an excellent post in its own right, but one thing that jumped out at me was the call to action from the author to follow him on Twitter at the end of his post. I liked that it was subtle, simple, and effective  (I followed him on Twitter). Naturally, I stole this idea for my own blogs. This method uses the Follow Button provided by Twitter. In this post, I will show you how to add your own “Follow Me on Twitter” Call to Action on  your own WordPress or Ghost blog.

WordPress

I used the Bottom of Every Post WordPress Plugin because I found that hacking the main wordpress loop caused my CTA to show up at the very bottom of a page rather than at the end of the post content. In addition, I made a small change to this plugin to make sure that it only appears on single posts rather than on the home page.

Edit bottom-of-every-post/bottom_of_every_post.php from the WordPress Plugins Editor and update the method to be:

if( is_single() && file_exists( $fileName )){

 /* open the text file and read its contents */

 $theFile = fopen( $fileName, "r");
 $msg = fread( $theFile, filesize( $fileName ));
 fclose( $theFile );
 
 /* detect the old message in code to try and eradicate my name and #
 showing up on strange websites that are run by lazy people */
 
 if( $msg == "<p>Call for an estimate 724-498-1551<br><a href=\"mailto:[email protected]\">[email protected]</a></p>" ){
 $msg = "<p>Thank you for installing the Bottom of every post WordPress plugin. To find out how to change or remove this message, read <a href=\"http://wordpress.org/extend/plugins/bottom-of-every-post/installation/\">the instructions</a>.</p>";
 }

 /* append the text file contents to the end of `the_content` */
 return $content . stripslashes( $msg );
 } else{

 /* if `the_content` belongs to a page or our file is missing
 the result of this filter is no change to `the_content` */

 return $content;
 }

The key here is

is_single()

Next, edit bottom-of-every-post/bottom_of_every_post.txt and add your call to action. Mine looks like this.

<p>
 If you made it this far, you should probably follow me on twitter. :) 
 <a class="twitter-follow-button" href="https://twitter.com/levlaz"> Follow @levlaz</a>
</p>

<script>window.twttr = (function(d, s, id) {
 var js, fjs = d.getElementsByTagName(s)[0],
 t = window.twttr || {};
 if (d.getElementById(id)) return t;
 js = d.createElement(s);
 js.id = id;
 js.src = "https://platform.twitter.com/widgets.js";
 fjs.parentNode.insertBefore(js, fjs);

 t._e = [];
 t.ready = function(f) {
 t._e.push(f);
 };

 return t;
}(document, "script", "twitter-wjs"));</script>

You only need to replace your twitter username in the above example and it should work as is. The end result is shown below:

Screen Shot 2017-04-10 at 4.44.14 PM.png

Ghost

I used ghosts code-injection tool to get this to work for every page. I added the following two scripts to the Blog Footer.

<script> 
 article = document.getElementsByClassName('post-content');
 child = document.createElement('p');
 cta = '<p class="follow"> If you read this far, thank you! Follow me on Twitter to stay up to date on what the fuss is all about.<br />';
 link = '<a class="twitter-follow-button" href="https://twitter.com/tralevnet"> Follow @tralevnet</a></p>'
 child.innerHTML = cta + link;
 article[0].appendChild(child);
</script>

<script>
 window.twttr = (function(d, s, id) {
 var js, fjs = d.getElementsByTagName(s)[0],
 t = window.twttr || {};
 if (d.getElementById(id)) return t;
 js = d.createElement(s);
 js.id = id;
 js.src = "https://platform.twitter.com/widgets.js";
 fjs.parentNode.insertBefore(js, fjs);

 t._e = [];
 t.ready = function(f) {
 t._e.push(f);
 };

 return t;
}(document, "script", "twitter-wjs"));
</script>

In the example above you need to change your call to action as well as your twitter username for it to work. I also added a custom style to the Blog Header

<style> 
.follow {
 line-height: 1.5;
 width: 50%;
 padding: 15px;
 border: dashed 1px lightgrey;
 font-size: smaller;
 color: #555;
 font-family: sans-serif;
 font-weight: bold;
 }
 
 .twitter-follow-button {
 margin-top: 10px;
 }
</style>

The end result looks like this:

Screen Shot 2017-04-10 at 4.46.57 PM.png

That’s pretty much it. You can go wild with the style to your hearts content. I like this simple CTA and I am excited to see how effective it is on my own blogs.

Using Font Awesome with Laravel

For some reason there is a whole thread on this seemingly simple tasks.

In a bootstrapped Laravel 5.4 instance the following worked for me.

Install Font Awesome with NPM

npm install font-awesome

Import font-awesome in your app.scss file

// resources/assets/sass/app.scss

// Font Awesome
@import "node_modules/font-awesome/scss/font-awesome";

Copy the fonts to public directory

Adding the following to your elixir config in the gulpfile

.copy('node_modules/font-awesome/fonts', 'public/fonts')

My complete gulpfile looks like this:

const elixir = require('laravel-elixir');

require('laravel-elixir-vue-2');

/*
 |--------------------------------------------------------------------------
 | Elixir Asset Management
 |--------------------------------------------------------------------------
 |
 | Elixir provides a clean, fluent API for defining some basic Gulp tasks
 | for your Laravel application. By default, we are compiling the Sass
 | file for your application as well as publishing vendor resources.
 |
 */

elixir((mix) => {
    mix.sass('app.scss')
       .copy('node_modules/font-awesome/fonts', 'public/fonts')
       .webpack('app.js');
});

Run Gulp

If you run gulp you should be able to now start using font-awesome everywhere in your app.

Dockerized Laravel and MySQL for local development

Docker is awesome. Its also quite useful for local development. The following Dockerfile and docker-compose.yml will be helpful if you want to do laravel development inside of docker.

I am using Ubuntu as a base, but you can probably use the official PHP image as well.

Dockerfile

FROM ubuntu:16.04

RUN apt update
RUN apt install -y php7.0 php7.0-zip php7.0-mbstring phpunit curl php7.0-mysql

RUN curl -sS https://getcomposer.org/installer | php 
RUN mv composer.phar /usr/local/bin/composer

RUN composer global require "laravel/installer"

RUN export PATH=$HOME/.config/composer/vendor/bin:$PATH

docker-compose.yml

version: '2'
services:
  app:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/code
    env_file: .env
    working_dir: /code
    command: bash -c 'php artisan migrate && php artisan serve --host 0.0.0.0'
    depends_on:
      - db
  db:
    image: "mysql:5.7"
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=$your_db
      - MYSQL_USER=$your_db_user
      - MYSQL_PASSWORD=$your_db_password
    volumes:
      - ./data/:/var/lib/mysql
    ports:
      - "3306:3306"

.env file

Your .env file is what Laravel uses when it starts up set up various things. The only real thing to change is your DB connection info. A full sample is shown below:

APP_ENV=local
APP_KEY=$your_app_key
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=$your_db
DB_USERNAME=$your_db_user
DB_PASSWORD=$your_db_password

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=

Gotchas

  1. In order to do stuff with the database you should add the following record to your local /etc/hosts file
    # /etc/hosts
    
    127.0.0.1 db
    
  2. You should still install npm and run npm install from your local machine so that you can do frontend stuff.
  3. Since we define - .:/code as a volume, this means that all of your local changes are immediately visible in the dockerized app.
  4. If you need to access the running app or db container you can do so with docker-compose run app bash or docker-compose run db bash