Tag Archives: wordpress

Host Your Own WordPress Site

Introduction

A while back I decided to host my blog on my own servers and cut the cord with WordPress.com. There were no hard feelings really; I just didn’t like the limited options for plugin choices I had. I didn’t like all the extra features they forced me to use that made my blog (response time) slower.

The standalone version of WordPress is much more scaled down and fits my requirements better. The standalone version also allows me to pick and choose from a sea of amazing plugins at my disposal.

Prerequisite

You’ll need to have full (Linux) shell access to the server you’re intending to host this from. I set up my hosting using a CentOS 6.x machine; therefore the instructions I identify here are for that. That said, the instructions won’t stray too far off (requiring a tweak here an there) from those people who choose to use other Linux distributions.

You’ll want to first install a few packages:

# You'll basically need PHP, MySQL 
# As root (or a user with sudoer's permission) type the following:
sudo yum -y install php-fpm php-mbstring php-mcrypt \
             php-mysql nginx mysql mysql-server gawk \
             openssl

Optional FTP Support

FTP support is really nice to have with a WordPress setup! You can use it to automate the installation of plugins through the admin page. This is great for situations where you’re setting up a WordPress account for someone who isn’t too teksavvy. It also allows grants your users enough access to install plugins through WordPress’s administration interface.

I strongly suggest you read my blog entry on Configuring and Installing VSFTPD on CentOS 6 if you’re interested in going this route.

It’s important to note that WordPress functions just fine without an FTP(S) server too!

Step 1 of 8: Prepare our Environment.

To make life really easy (so you can cut and paste this right to your command line without any effort at all), lets create some environment variables.

Please note that this step MUST be ran before any of the other steps are. If you’re returning to this blog entry to resume from a step you left off at, be sure to apply these environment variables again!

Please note that you must be root or have sudoer’s permission to be able to perform any of these tasks successfully on your server.

# Our WordPress user
WPUSER=nuxref

# The FQD you will be serving your data from.  If you
# don't have your own domain, then set this to an
# underscore '_' (without the quotes '')
WPURL=nuxref.com

# Some Database Information
# - what are we going to call our database name?
DBNAME="wordpress_$WPUSER"
# - it's easier to just use the WordPress user account here
#   but if you want to change it to something else; here is
#   where you can do it:
DBUSER=$WPUSER
# - we will want to create a confusing password that others
#   can't guess. I don't recommend you use what i've identified
#   here because anyone else who knows you read my blog will
#   guess this first.  But here is where you should set your
#   database password you intend to use.
DBPASS="v3ryC0nFU51Ng-Pw%"

# If you plan on creating an FTP Account; you'll want to
# populate these variables too. This account does not have
# to be the same as the $WPUSER account. In fact making it
# different (even just slightly) would be a good idea!
# Below i just add '-ftp' to the end of the already
# determined user above.  Feel free to change this.
WPFTPUSER="$WPUSER-ftp"
# Set an FTP password; It would be a good idea to not use
# the one identified below as it's merely display only.
# some special character don't work with VSFTPD (like '!')
# if you plan on using it .
WPFTPPASS="4nt3rP455%rd"

# The following is only used for our SSL Key Generation
COUNTRY_CODE="7K"
PROV_STATE="Westerlands"
CITY="Lannisport"
SITE_NAME="Life as a Lannister"

Step 2 of 8: Create our User Account

You’ll want to create an isolated environment for our client (or you) to work within. By securing an environment; in the event anything is ever compromised, destruction will be limited to what we allow our client access to.

# First create a system directory to host our project.
sudo mkdir -p /opt/$WPUSER/html/static

# Create a dummy, favicon.ico file for now. If you feel
# ambitious, Google this if you're not sure what it's for
# so you can place your own custom one here
touch /opt/$WPUSER/html/static/favicon.ico

# Create System User
sudo useradd nuxref -M --system \
   --comment "$WPUSER WordPress Account" \
   --home /opt/$WPUSER \
   --shell /sbin/nologin

# Secure our new directory we created
chmod 711 /opt/$WPUSER
chown -R $WPUSER.$WPUSER /opt/$WPUSER

If you’ve followed my blog on Securing and Protecting Your CentOS 6 System then you might have wisely chosen to set up disk quotas. If not; then you can skip over to the next step.

# Detect the device using our home directory
DEV=$(df -l -P /opt/$WPUSER | awk 'END{print $1}')
# Restrict Users Disk Quota to 600MB
sudo setquota -u $WPUSER 180000 600000 0 0 $DEV

Step 3 of 8: Generate SSL Keys

We need to generate some Secure Socket Layer (SSL) keys so that we can provide a secure connection for logins. Otherwise our passwords we choose to work with the site could be exposed.

To make things simple, you can use my genssl tool first discussed in an earlier blog I wrote here. available for download from my github page and then just do the following:

# Generate a self signed key:
genssl -s $WPURL
# Install it:
sudo install -m 0400 $WPURL.key /etc/pki/tls/private/$WPUSER.key
sudo install -m 0444 $WPURL.crt /etc/pki/tls/certs/$WPUSER.crt

Or you can simply do the following:

# The following will generate SSL Keys (if you don't have any already)
sudo openssl req -nodes -new -x509 -days 730 -sha256 -newkey rsa:2048 
   -keyout /etc/pki/tls/private/$WPUSER.key 
   -out /etc/pki/tls/certs/$WPUSER.crt 
   -subj "/C=$COUNTRY_CODE/ST=$PROV_STATE/L=$CITY/O=$SITE_NAME/OU=IT/CN=$WPURL"
 
# Permissions; protect our Private Key
chmod 400 /etc/pki/tls/private/$WPUSER.key
 
# Permissions; protect our Public Key
chmod 444 /etc/pki/tls/certs/$WPUSER.crt

Step 4 of 8: Install our WordPress Bundle

Now we need to Download and install WordPress into our environment.

# WordPress Configuration
# Acquire latest version from here https://wordpress.org/download/
# (At the time it was 4.4.2)
wget --no-check-certificate https://wordpress.org/latest.tar.gz -O wordpress.$(date +'%Y.%m.%d').tgz

# Extract our downloaded copy
sudo tar xvfz wordpress.$(date +'%Y.%m.%d').tgz \
    -C /opt/$WPUSER/html --strip 1

# Apply some more permissions
sudo find /opt/$WPUSER/html -type d -exec chmod 755 {} \;
sudo find /opt/$WPUSER/html -type f -exec chmod 664 {} \;
sudo find /opt/$WPUSER/html -exec chown $WPUSER.apache {} \;

# Grant write permissions to a few tools our plugin installers
# will need access to later on:
find /opt/$WPUSER/html/wp-content/ -type d -exec chmod 775 {} \;
sudo chmod 660 /opt/$WPUSER/html/wp-config.php

Step 5 of 8: Configure and Prepare our Database

Now we need to configure our MySQL (or MariaDB) database. First make sure it is running:

# The below command will start the database if it isn't
# already running:
sudo service mysqld status &>/dev/null || \
   sudo service mysqld start

# Next make sure you're system is configured to start
# the database each and every time your server turns on
sudo chkconfig --level 345 mysqld on

Now we need to prepare our database that WordPress can use.

# SQL Initialization
( cat << _EOF
CREATE DATABASE $DBNAME;
GRANT ALL PRIVILEGES ON $DBNAME.* TO "$DBUSER"@"localhost" IDENTIFIED BY " $DBPASS";
FLUSH PRIVILEGES;
_EOF
) | sudo mysql

Step 6 of 8: Configure our Web Hosting Service

Okay now we need to host our website. Effectively linking the database we just prepared with the WordPress software we just installed. We do this as follows using NginX:

# By default (assuming a CentOS installation), we can
# plug into our configuration by writing our data in
# /etc/nginx/conf.d/
# So lets do just that:
cat << _EOF > /etc/nginx/conf.d/wordpress_$WPUSER.conf
#
# $WPUSER WordPress Web Hosting
#
server {
    # Support Web Traffic at port 80
    listen       80;
    server_name  $WPURL;
    root   /opt/$WPUSER/html;

    # Our log files
    access_log  /var/log/$WPUSER/$WPUSER.access.log  main;
    error_log  /var/log/$WPUSER/$WPUSER.error.log;

    # Our main handler
    location / {
        root   /opt/$WPUSER/html;
        index  index.html index.htm index.php;
        # Support Permalink changes
        try_files \$uri \$uri/ /index.php?q=\$request_uri;
    }

    # Anyone logging into our site should do it securely
    location /wp-admin/ {
       # Always redirect to secure site
       rewrite ^/(.*) https://$host/\$1 permanent;
    }
    location /wp-login/ {
       # Always redirect to secure site
       rewrite ^/(.*) https://\$host/\$1 permanent;
    }

    error_page  404              /404.html;
    location = /404.html {
        root   /usr/share/nginx/html;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # Support the favicon (for those wanting to use it)
    location = /favicon.ico {
        root   /opt/$WPUSER/html/static;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php\$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
        include        fastcgi_params;
    }

    # Deny access to the wp-config file
    location ~ /wp-config\.php {
        deny  all;
    }
}

server {
    # We should listen on a secure URL too so that we can
    # hide our admin login credentials from prying eyes
    listen       443;
    server_name  $WPURL;
    root   /opt/$WPUSER/html;

   ssl on;
   ssl_certificate /etc/pki/tls/certs/$WPUSER.crt;
   ssl_certificate_key /etc/pki/tls/private/$WPUSER.key;
   ssl_session_timeout  5m;

   # Secure our site by only allowing the TLS protocol
   ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
   ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK';
   ssl_prefer_server_ciphers on;
   ssl_session_cache  builtin:1000  shared:SSL:10m;

   access_log  /var/log/nginx/$WPUSER.access.log  main;
   error_log  /var/log/nginx/$WPUSER.error.log;

   location / {
      root   /opt/$WPUSER/html;
      index  index.html index.htm index.php;
      # Support Permalink changes
      try_files \$uri \$uri/ /index.php?q=\$request_uri;
   }

   error_page  404              /404.html;
   location = /404.html {
      root   /usr/share/nginx/html;
   }

   # redirect server error pages to the static page /50x.html
   #
   error_page   500 502 503 504  /50x.html;
   location = /50x.html {
       root   /usr/share/nginx/html;
   }

   # Handle favicon
   location = /favicon.ico {
       root   /opt/$WPUSER/html/static;
   }

   # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
   #
   location ~ \.php\$ {
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
      include        fastcgi_params;
   }

   # Deny access to the wp-config file
   location ~ /wp-config\.php {
      deny  all;
   }
}
_EOF

Now restart our web services

# Ensure our web browser and php handler will start
# even if our server is restarted
chkconfig --levels 345 php-fpm on
chkconfig --levels 345 nginx on

# The following just makes sure we reload and take
# on our new configuration.  If we're not running
# then we start the services up
service php-fpm status &>/dev/null && \
   service php-fpm restart || \
   service php-fpm start

service nginx status &>/dev/null && \
   service nginx restart || \
   service nginx start

Step 7 of 8: Optionally Setup an FTP Account

Most people can skip this step; it again presumes you’ve followed my other blog on Configuring and Installing VSFTPD on CentOS 6. If you have not gone here or have set up FTP your own way, you can also skip this step and move on with Configuring WordPress.

# Create a WordPress Plugins FTP Account
echo $WPFTPUSER >> /etc/vsftpd/users.passwd
echo $WPFTPPASS >> /etc/vsftpd/users.passwd
 
# Protect Password
chmod 600 /etc/vsftpd/users.passwd
chown root.root /etc/vsftpd/users.passwd
 
# Now convert content into a db structure
db_load -T -t hash -f /etc/vsftpd/users.passwd /etc/vsftpd/virtual.users.db
chmod 600 /etc/vsftpd/virtual.users.db
chown root.root /etc/vsftpd/virtual.users.db
 
cat << _EOF > /etc/vsftpd/virtual.users/$WPFTPUSER
local_root=/opt/$WPUSER/html
# -------------------------------------------------------------------------
# User
# -------------------------------------------------------------------------
guest_enable=YES
guest_username=apache
local_root=/opt/$WPUSER/html
# -------------------------------------------------------------------------
# Permissions
# -------------------------------------------------------------------------
# write_enabled is required if the user is to make use of any of the
# anon_* commands below
write_enable=YES
# give the user the ability to make directories
anon_mkdir_write_enable=YES
# give the user the ability delete and overwrite files
anon_other_write_enable=YES
# give the user the ability upload new files
anon_upload_enable=YES
# Give the user permission to do a simple directory listings
dirlist_enable=YES
# Give the user permission to download files
download_enable=YES
# if the user has can upload or make new directories, then this will be
# the umask applied to them
anon_umask=0002
# delete failed uploads (speaks for itself)
delete_failed_uploads=NO
_EOF
 
sudo chmod 600 /etc/vsftpd/virtual.users/$WPFTPUSER
sudo chown root.root /etc/vsftpd/virtual.users/$WPFTPUSER

# Ensure our FTP Server will restart if our server
# is ever restarted:
sudo chkconfig --level 345 vsftpd on
# Update Service (to read in new configuration)
sudo service vsftpd status &>/dev/null && \
    service vsftpd restart || \
    service vsftpd start

Step 8 of 8: Configure WordPress

If you successfully pulled off all of the earlier steps, then you shouldn’t have any trouble from this point forward. The hard part is done with!

Wordpress Database SetupWordpress InstallationYou now need to open up your browser and access your new WordPress website to continue with the setup. Simply visit your website by browsing to http://your.wordpress.url/ (whatever you set this up as).

First you’ll be immediately presented with webpage that needs some information about the database we set up back in Step 5 (an environment variables defined in Step 1.

After you press the [Submit] button, you’ll then be asked to define some basic information about the blog you intend to set up. You can change all this later, so don’t worry. The important fields here are the administrator user and password you create.

You’re done now and ready to use WordPress

Great WordPress Plugins

The following plugins are worthy of a mention:

Credit

Please note that this information took me several days to put together and test thoroughly. I may not blog often; but I want to re-assure the stability and testing I put into everything I intend share.

If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.

Offline Blogging Solutions with CentOS 6

Introduction

BloGTK v2.0
BloGTK v2.0

Offline Blogging in Linux doesn’t offer us a wide selection of free (open source) choices. Of the choices we do have at our disposal each have their own pros and cons which are really just bias opinions we’ll all carry with each other. This blog isn’t going to tell you which product is better and which one isn’t. It will provide you some alternatives to what’s already available and allow you to choose on your own. I also make additionally options available to you here as well should you choose to try them.

Keep in mind I run CentOS 6 as my primary OS (currently), so I focus primarily on making these products work on this distribution. But this doesn’t mean that all of the source RPMs I provided won’t compile for you in another distribution.

Drivel v3.0.0 Login Screen
Drivel v3.0.0 Login Screen

Open Source Offline Blogging Choices

The below outline some of the choices I found to be worth of digging further in:

I’m not sure what the status is on all of these project themselves. At this current time, I have to assume that both Drivel and BloGTK are some what dead since the last update to either of them

Gnome Blog v0.9.2
Gnome Blog v0.9.2

was back in late 2009. Meanwhile the last update made to Gnome Blog was in early 2010.

It is said that beggars can’t be choosers. So rolling with that in mind and the Open Source solutions available to us, we’ll accept what is offered and move on.

Hand over your work

With pleasure; it really didn’t take any time at all to package these properly.

Drivel (v3.0.0) took the most time to package; but even that didn’t take much effort. Drop Line provided a spec file of their own which didn’t work out of the box. It also didn’t include all the necessary dependencies. For this reason I just spun my own version of it. Have a look here if you want to see the spec file I generated.

BlogGTK v2.0 didn’t take me hardly any time at all. They didn’t change the installation that much from v1.1. The fact that it’s python based; there really isn’t a whole lot too it. You can view the spec for yourself if you’re interested.

Alternatively you can just fetch bloGTK from Pkgs.org which does such a great job organizing packages other people have put together. It’ll probably be an older version (as it was for me). At the time I wrote this blog it was BloGTK v1.1 on Pkgs.org hosted by RPMForge. It might be different when you try.

Gnome Blog was another one that actually packaged it’s own spec file within the official packaging. But the file was drastically missing dependencies and would not work out of the box at all. I had to massage it quite a bit; you can view the spec file here if you feel the need.

I will never trust you; I’ll build it for myself

Still feeling that way? No problem; here is how you can do it:

First off, I’m not a big fan of compiling code as the root user on the system I work with daily.   I am however a big fan of a tool called ‘mock‘ which allows us to develop software as root except within a safe virtual environment instead of our native one. I am also a big fan of package management; whether its a .DEB (Debian Package) or .RPM (Red Hat Package) for obvious reasons. For this tutorial; I’ll stick with RPMs since it’s what CentOS uses. We’ll prepare the RPMs and preform all our compilations within the mock environment.

# Install 'mock' into your environment if you don't have it already
# This step will require you to be the superuser (root) in your native
# environment.
yum install -y mock

# Grant your normal every day user account access to the mock group
# This step will also require you to be the root user.
usermod -a -G mock YourNonRootUsername

At this point it’s safe to change from the ‘root‘ user back to the user account you granted the mock group privileges to in the step above.  We won’t need the root user again until the end of this tutorial when we install our built RPM.

# Optionally fetch bloGTK v2.0
wget https://launchpad.net/blogtk/2.0/2.0/+download/blogtk-2.0.tar.gz
wget --output-document=blogtk.spec https://www.dropbox.com/sh/9dt7klam6ex1kpp/GR0uXU6PaC/20131008/blogtk.spec?dl=1

# Optionally fetch Drivel 3.0.0
wget --output-document=drivel-3.0.0.tar.bz2 http://prdownloads.sourceforge.net/drivel/drivel-3.0.0.tar.bz2?download
wget --output-document=drivel.spec https://www.dropbox.com/sh/9dt7klam6ex1kpp/MKD34uuBMs/20131008/drivel.spec?dl=1

# Optionally fetch gnome-blog v0.9.2
wget http://ftp.gnome.org/pub/GNOME/sources/gnome-blog/0.9/gnome-blog-0.9.2.tar.gz
wget --output-document=gnome-blog.spec https://www.dropbox.com/sh/9dt7klam6ex1kpp/O9nJdxoJMZ/20131008/gnome-blog.spec?dl=1

# Initialize Mock Environment
mock -v -r epel-6-x86_64 --init

# bloGTK dependencies
mock -v -r epel-6-x86_64 --install 
  python pygtk2 pygtk2-libglade desktop-file-utils

# Drivel dependencies
mock -v -r epel-6-x86_64 --install 
  gnome-doc-utils intltool gtk2 gtkspell-devel 
  glib-devel gtk2-devel GConf2-devel 
  gnome-vfs2-devel gtksourceview2-devel 
  libsoup-devel libxml2-devel

# gnome-blog dependencies
mock -v -r epel-6-x86_64 --install 
  pygtk2-devel gettext intltool 
  desktop-file-utils GConf2-devel 
  python-devel

mock -v -r epel-6-x86_64 --copyin blogtk.spec /builddir/build/SPECS
mock -v -r epel-6-x86_64 --copyin gnome-blog.spec /builddir/build/SPECS
mock -v -r epel-6-x86_64 --copyin drivel.spec /builddir/build/SPECS

mock -v -r epel-6-x86_64 --copyin drivel-3.0.0.tar.bz2 /builddir/build/SOURCES
mock -v -r epel-6-x86_64 --copyin gnome-blog-0.9.2.tar.gz /builddir/build/SOURCES
mock -v -r epel-6-x86_64 --copyin blogtk-2.0.tar.gz /builddir/build/SOURCES
mock -v -r epel-6-x86_64 --shell

# Within Shell Environment, Build the Desired RPM
rpmbuild -ba builddir/build/SPECS/drivel.spec
rpmbuild -ba builddir/build/SPECS/blogtk.spec
rpmbuild -ba builddir/build/SPECS/gnome-blog.spec

# exit shell (or press Cntrl-D)
exit

# Copy out your blogger of interest
mock -v -r epel-6-x86_64 --copyout /builddir/build/SRPMS/drivel-3.0.0-1.el6.src.rpm .
mock -v -r epel-6-x86_64 --copyout /builddir/build/RPMS/drivel-debuginfo-3.0.0-1.el6.x86_64.rpm .
mock -v -r epel-6-x86_64 --copyout /builddir/build/RPMS/drivel-3.0.0-1.el6.x86_64.rpm .

mock -v -r epel-6-x86_64 --copyout /builddir/build/SRPMS/blogtk-2.0-1.el6.src.rpm .
mock -v -r epel-6-x86_64 --copyout /builddir/build/RPMS/blogtk-2.0-1.el6.noarch.rpm .

mock -v -r epel-6-x86_64 --copyout /builddir/build/SRPMS/gnome-blog-0.9.2-1.src.rpm .
mock -v -r epel-6-x86_64 --copyout /builddir/build/RPMS/gnome-blog-0.9.2-1.x86_64.rpm .
mock -v -r epel-6-x86_64 --copyout /builddir/build/RPMS/gnome-blog-debuginfo-0.9.2-1.x86_64.rpm .

# Install your blogger of choice; you'll need to be root or
# have sudoers permission to do this:
yum localinstall drivel-3.0.0-1.el6.x86_64.rpm
yum localinstall blogtk-2.0-1.el6.noarch.rpm
yum localinstall gnome-blog-0.9.2-1.x86_64.rpm

Drivel & WordPress

Drivel supports WordPress with a small with the following configuration:

  1. Configure your User/Pass as you normally would have
  2. Set the Movable Type to Journal type
  3. Set the Server Address field to be http://yourusername.wordpress.com/xmlrpc.php. For example I would have put http://nuxref.wordpress.com/xmlrpc.php for my own blog.

Another thing to note about Drivel is I was unable to retrieve a list of recent posts made to the WordPress server. However every other aspect of the tool appears to fine. People using different blog engines may not notice any problem at all.

Gnome-Blog & WordPress

  1. Set the Blog Type to Self-Run Other
  2. Set the Blog Protocol to MetaWeblog
  3. Set the XML-RPC URL field to be http://yourusername.wordpress.com/xmlrpc.php. For example I would have put http://nuxref.wordpress.com/xmlrpc.php for my own blog.
  4. Configure your User/Pass as you normally would have

Not Open Source, but other Free Alternatives:

  • ScribeFire:A plugin exists for Firefox & Chrome users called ScribeFire which also enables blogging functionality from within your browser. It’s worth noting as another alternative if you want it. It doesn’t involve extra packaging since it can be installed from within your browser.
  • Thingamablog: Another free solution; Thingamablog provides the binaries directly from their website here

Credit

If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.

If I forgot any (Open Source) Offline Bloggers that you know about; please let me know. I have no problem updating this blog to accommodate it.

Sources

I referenced the following resources to make this blog possible: