Since the beginning of the Covid-19 crisis, as with most aspects of life, learning and education have had to adapt, by necessity, to remote access.  In that field, Canvas-LMS (from Instructure) clearly maintains the lead position as the go-to suite for providing educational content over remote channels, be it from elementary schools up through universities and even corporate training. 

Instructure does offer hosted Canvas solutions, providing turn-key access to organizations that may not have the technical staff or the same closely-held security requirements as public and private enterprises, which more likely deploy a Canvas system on local or cloud 'hardware' more directly under their control. Instructure's main support documentation describes paths to install Canvas-LMS on either MacOS (OS X) or Debian || Ubuntu-based Linux distributions.  However, Canvas-LMS is a complex suite; challenging to install even for experienced Systems Administrators and Developers and requiring a narrow list of supporting framework versions that may no longer be "default" packages in the current repositories ("repos"). There are also a healthy number of un-affiliated sites with their own "how-to" on installing Canvas; but again, these are focused on Debian-based installs. 

This article, though, is about the path less taken: Canvas-LMS on Red Hat Enterprise Linux 8. (Please watch for a future revision of this article adjusting these steps for install on either Alma or Rocky.)

The qCOW's Outta the Red Barn:

As a Red Hat partner, when Moser Consulting was tasked with providing an LMS (learning management system) solution for a prospective client, we immediately went to work creating (seemingly for the first time) a Canvas-LMS horizontal suite (separate database and web servers) for RHEL8. Ultimately, we knew this would end up as a cloud-based service, but that can be impractical for initial testing, so the Linux Engineer started by creating two local VMs from a base qcow2 of RHEL8 and spinning these up as local KVMs on a development workstation. 

https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.4/x86_64/product-software

NOTE:  all the commands presume the user has root or wheel level access to the systems. If you cannot `sudo su - root` on the system, these commands should be preceded by `sudo …`. 

After setting the root password of the qcow2 file, re-name this 'original' as a back-up file and make two copies, one for the database server and a second for the web or "App" server. 

Boot up both of the KVMs and register them with Red Hat from the command-line: 

subscription-manager register 

...using the appropriate credentials.  Also, configure both environments with the following settings and repos:

subscription-manager config --rhsm.manage_repos=1
subscription-manager repos --enable rhel-8-for-x86_64-appstream-rpms

subscription-manager repos --enable codeready-builder-for-rhel-8-x86_64-rpms

Setting Up the DataBase Server:

Of the two servers in the suite, the PostgreSQL DB server was by far the easier of the two to stand up, so let's address that one first. For the test environment, 2 vCPUs and 2GB RAM are sufficient, but if this is the cloud instance (intended for Production) use at least 4 vCPUs and no less than 8GB RAM. With some Cloud providers (AWS), there might not be a separate VM / container; in such cases, spin up a PostgreSQL database and configure with the your chosen username and password during the initial setup. 

INSTALLATIONS:

Install the PostgreSQL suite with:

yum -y install @postgresql 

This will install all the necessary packages including postgresql and postgresql-server.  Then, initialize the database service:

postgresql-setup --initdb

As part of installation, the system user postgres is created. It is highly recommended to change this system user's password for security reasons:

passwd postgres

New password:

Retype new password:

Create a database user (not a system user) for the non-root level user that will access and handle the Canvas-LMS database from the App server. This can be done by either switching to the postgres user

(su - postgres) and logging into PostgreSQL with the `psql` command or with the following syntax:

su - postgres -c "echo -e \"${canvas_userpw}\n${canvas_userpw}\" | \ 
createuser canvas --no-superuser --no-createdb --no-createrole --pwprompt;"

… where canvas is the username and ${canvas_userpw} is some fairly complex password the application server can use to log in remotely. 

Also, the canvas user will need a database named the same:

su - postgres -c "createdb canvas;"

CONFIGURATIONS:

In its default configuration, PostgreSQL will only 'listen' for incoming connections on localhost:5432 so, to enable a(ny) separate application server(s) to connect, edit the file /var/lib/pgsql/data/postgres.conf and look for a line similar to:

#listen_addresses = 'localhost'

Uncomment it (delete the 'hash-tag' at the front) and add the IPv4 address of this database server something like this:

listen_addresses = 'localhost,${DB_IPv4}'

… where ${DB_IPv4} is the LAN IPv4 address of the DB server.

 

The next file to adjust will be /var/lib/pgsql/data/pg_ident.conf -- and it goes without saying, but we're gonna say it anyway:  always make a back-up copy of any file you're editing BEFORE you make any changes. AFTER copying the file, a line identical to the one above should be added to the pg_indent.conf file:

listen_addresses = 'localhost,${DB_IPv4}'

(Even if this line is not in the file.) 

The pg_hba.conf file in /var/lib/pgsql/data controls access from other hosts to the databases, so in this file please configure the LAN IP address(es) of the App Server(s):

# TYPE      DATABAE       USER                ADDRESS                 METHOD

host        sameuser      canvas              $IPv4_App1              md5

host        sameuser      canvas              $IPv4_App2              md5

PASSWORD LOCK-DOWN:

With the above steps successfully completed, it's advisable to set the postgres database user's password as the final step in configuring your DB instance.  Note: this is a unique step to resetting the password for the postgres user's system account above. 

The quickest method to do this is similar to the method used earlier for setting the canvas database user's password:

su - postgres -c "psql -U postgres -d postgres -c \"ALTER USER postgres WITH \
 ENCRYPTED PASSWORD '$postgres_userpw';\""

The value of $postgres_userpw should be in plain-text; the database will actually perform the encryption. 

Setting Up the Application Server(s):

With the DB server, those steps will always be fairly agnostic, capable of working with any decent web-server out there. For the App Server(s), Moser focused on getting Canvas-LMS working on Apache 2.4 WebServer. What's described here will be specific to that set-up.  However, anyone familiar with web-server configuration may still find these steps useful in adjusting for their http server of choice (eg: NginX, MS Server, etc.). 

System requirements are crucial during the initial installation.  Several attempts 'froze' because we did not give the environments enough resources for compiling the many assets of Canvas-LMS. At a minumum, to compile the application side of Canvas-LMS the first time, the system needs 12GB RAM and 4x vCPUs. Post-compile, clones or containers may require fewer resources; we did not perform those tests. 

Also, Canvas-LMS is fairly picky about what version of certain frameworks it requires. It took Moser's team a good deal of experimentation in the test environments to discern the optimal working versions of each component AND the most effective order in which to install them. This is what worked, eventually, for us, but your mileage may vary. Adjusting or altering the order may work better for you…

INSTALLATIONS:

In testing, we found the system did not always 'digest' everything properly if listed as one big install. We found it more effective to break up the package installs as follows:

yum -y install gnupg2 postgresql

(Note: this installs the PostgreSQL Client on the App server.)

yum -y install git-core

yum -y install xmlsec1 xmlsec1-devel xmlsec1-openssl libxml2-devel \

 libpq-devel libtool-ltdl-devel

yum -y install sqlite-devel make gcc gcc-c++ bzip2 zlib-devel

Most of the Debian-based documentation recommends installing the canvas suite in /var/canvas, but for a variety of reasons we chose to use /opt/canvas; in the end there is essentially no difference.

CAN_DIR=/opt/canvas

CAN_USER=canvas

useradd -r -U -c "Canvas-LMS system user" -d $CAN_DIR $CAN_USER

echo $canvas_userpw | passwd --stdin $CAN_USER

mkdir -p $CAN_DIR

chown -R ${CAN_USER}: $CAN_DIR

cd $CAN_DIR

 

--------------------------------------------------------------------

And now a brief pause for a specific, cloud-based configuration…

This is NOT necessary on a 'local' test system OR one with a cloud provider where you'd have your own, separate, DB server, because the canvas database user would have already been added to the DB in the above configurations. IF, however, you're configuring these App servers on a cloud provider that does NOT allow you a separate DB server (cough – AWS -cough!!) here is where we could now, from the App server, add the canvas database user to the PostgreSQL DB instance using the postgres database user's account and credentials:

export PGPASSWORD="${postgres_userpw}"

psql -h $DB_IPv4 -U postgres -c "CREATE DATABASE ${CAN_USER}"

psql -h $DB_IPv4 -U postgres -c "CREATE USER ${CAN_USER} WITH ENCRYPTED \

 PASSWORD '${canvas_userpw}';"

unset PGPASSWORD 

… we now return to our regularly scheduled configurations…

--------------------------------------------------------------------

 

Continuing on, install the core of Canvas-LMS:

git clone https://github.com/instructure/canvas-lms.git $CAN_DIR

git checkout stable    

 

Install Ruby; and here Canvas-LMS will be particular about the versioning, so depending on when you're reading this, adjust accordingly:

curl -sSL https://rvm.io/mpapis.asc | gpg2 --import -

curl -sSL https://rvm.io/pkuczynski.asc | gpg2 --import -

curl -sSL https://get.rvm.io | bash -s stable

source /etc/profile.d/rvm.sh

rvm install 2.6.8 --default

gem install bundler -v 2.2.21

Getting the proper bundler installed and working took probably the most effort of any single piece of the whole project, so don't be surprised if you have to take a couple of runs up the hill. 

chown -R ${CAN_USER}: $CAN_DIR

bundle config set --local path 'vendor/bundle'

bundle config set --local without 'pulsar' 

As most of this IS performed as the root user, there are a number of times when `chown -R...` will be used to update the ownership of everything in the $CAN_DIR. Similarly, certain commands need to be run as other users, such as:

su - canvas -c "bundle install" 

 

Next, install NodeJS:

curl -sL https://rpm.nodesource.com/setup_14.x | bash -

yum -y install nodejs

echo "The NodeJS version is `node -v` "

(The above command is optional for traceback and/or debugging.) 

npm install -g npm@latest

 

Canvas-LMS prefers using yarn for a number of its reliant packages: who are we to argue?

rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg

curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo

yum -y install yarn 

And again, the yarn installs should be performed as the canvas user:

su - canvas -c "yarn install"

 

Proceed to install Python -- Moser chose to go with Python3x and no post-install issues were noted:

yum -y install python3

 

Phew!  That's a lotta installations (but we're not done yet).  Next it's on to …

 

CONFIGURATIONS:

Canvas puts sample and (usually) active configurations in $CAN_DIR/config/ (/var/canvas/config or /opt/canvas/config/ in our case), and the naming convention is self-evident. Files of note that must be active to get Canvas-LMS working are:

  • amazon_s3.yml
  • database.yml
  • delayed_jobs.yml
  • domain.yml
  • file_store.yml
  • outgoing_mail.yml
  • security.yml
  • external_migration.yml

Please make a backup copy prior to altering any of the originals. 

To get eMail working from Canvas-LMS, edit the file outgoing_mail.yml

 vi $CAN_DIR/config/outgoing_mail.yml

...and put the logical credentials in there. Also, based on your MX, you may want to set secure ports for the SMTP agent. 

In database.yml there will be three sections: test, development, and production. All three usually have the same keys:

production:

  adapter: postgresql

  encoding: utf8

  database: canvas

  username: canvas

  password: $canvas_userpw

  timeout: 5000

Adjust for your values, of course. Post-install, the timeout value may need to be adjusted upward on low-resource environments, like a test or dev, to get Canvas-LMS fully functioning. 

There are also test, development, and production sections in the domain.yml file, but fewer pertinent keys:

production:

  domain: $FQDN || $IPv4_App1 [:3000]

  ssl: true || false  

The domain: key requires either a fully qualified domain name or an IP address, and the :3000 port information is optional depending on your intended deployment. 

The most hassle-free way to set the encryption key in the security.yml is:

rm -f $CAN_DIR/config/security.yml

cd $CAN_DIR/config

hex_code=`openssl rand -hex 20`

cat security.yml.example | sed -e "s/encryption_key\:.*$/encryption_key\:\ ${hex_code}/g" >> security.yml

cd $CAN_DIR

 

And before proceeding to the next round of installs, create these files or directories:

mkdir -p log tmp/pids public/assets app/stylesheets/brandable_css_brands

touch app/stylesheets/_brandable_variables_defaults_autogenerated.scss

touch Gemfile.lock

touch log/production.log

chown -R ${CAN_USER}: $CAN_DIR

INSTALLATIONS v2:

It may seem odd, but most Canvas-LMS configuration guides we came across again ran yarn and bundle install at this point:

su - canvas -c "yarn install"

su - canvas -c "bundle install"

chown -R ${CAN_USER}: $CAN_DIR 

 

Now comes probably the biggest compile:

su - canvas -c "RAILS_ENV=production COMPILE_ASSETS_STYLEGUIDE=0 bundle exec \ rake canvas:compile_assets"  

This will take a chunk of time, and once it's done, set ownership of everything in the $CAN_DIR again:

chown -R ${CAN_USER}: $CAN_DIR

chmod 400 config/*.yml

 

POPULATION:

When populating the Canvas database on the DB server, a number of values are required to be set so that the site's administrator logging into the WebUI can log in.  If not provided in a file or as part of a scripted command, the Canvas-LMS will prompt for these values during initial_setup. We found it easier to set these as environmental variables for the Canvas user as part of that command string:

su - canvas -c "CANVAS_LMS_ADMIN_EMAIL='some.body@example.com' \

CANVAS_LMS_ADMIN_PASSWORD=${somebody_userpw} \

CANVAS_LMS_ACCOUNT_NAME=${organization_name_here} \

CANVAS_LMS_STATS_COLLECTION=opt_out \

RAILS_ENV=production bundle exec rake db:initial_setup  

This also may take a bit of time; during the setup, you could log into the database separately and confirm that tables and data are being written to the Canvas database.

 

INSTALLATIONS v3:

We're almost back in Kansas, Dorathy.  Just need to install a web-server and Phusion Passenger:

yum -y install httpd mod_ssl

HTTP_USER=apache

HTTP_GRP=apache

HttpDir='/etc/httpd' 

PassengerConf=${HttpDir}/conf.d/passenger.conf 

 

To enable the Passenger repository, you'll have to lean on Fedora and EPEL for a minute:

yum -y install https://dl.fedoraproject.org/pub/epel-release-latest-$(< \

 /etc/redhat-release tr -dc '0-9.' | cut -d \. -f1).noarch.rpm

yum-config-manager --enable epel

yum clean all && yum -y update  

 

Then pass along the passenger repo and install passenger:

curl --fail -sSLo /etc/yum.repos.d/passenger.repo \

 https://oss-binaries.phusionpassenger.com/yum/defnitions/el-passenger.repo

yum -y install mod_passenger || yum-config-manager --enable cr \

 && yum -y install mod_passenger

gem pristine executable-hooks 


A fact to be aware of: passenger requires a license to run as a "production" environment. More details can be found on their website at https://www.phusionpassenger.com/pricing.

 

Canvas-LMS will need a VirtualHost configuration file in $HttpDir/conf.d/ and an easily customizable template is provided by Instructure on the github site at:

https://github.com/instructure/canvas-lms/wiki/Production-Start 

 

 

 

Once you've got your /etc/httpd/conf.d/canvas.conf file in place, start up and enable the Apache WebServer with:

systemctl enable --now httpd 

Validate the passenger has installed properly:

/usr/bin/passenger-config validate-install --auto

echo $?  

(If all is working, there should be no output, hence the need to confirm with the $? return.)

If all looks good there, a couple of items need to be adjusted in the file /etc/httpd/conf.d/passenger.conf. This file usually comes out of the box with the PassengerRuby value set to something like:

PassengerRuby /usr/bin/ruby

However, Canvas-LMS required those specific versions of Ruby, and it’s likely the default value will bork the WebUI.  So run:

rvm which ruby

...and put that returned value in the passenger.conf file:

PassengerRuby /usr/local/rvm/rubies/ruby-2.6.8/bin/ruby

While you're there, just below that PassengerRuby line, specify that the Canvas user will be the one using passenger with this line:

PassengerDefaultUser canvas

That could be considered optional, but this can be a pretty finicky part of the overall installation and every little bit helps. 

Restart Apache:

systemctl restart httpd

 

Error screen when logging in to Canvas

TESTING + DEBUGGING:

In a web browser, navigate to the IP or URL of your Apache WebServer. Passenger dynamically spawns, so this may take a bit of pinwheeling before you see a Canvas-LMS login screen. Once you see a self-evident login screen, log in with the credentials set up in the POPULATION section above. 

IF you get a screen that's similar to "Something went wrong" with a big "X", it means that passenger is almost working. Check in /etc/httpd/logs/error_log for any passenger-related errors, though it’s likely a timeout issue on a smaller test system, in which case add this line to the passenger.conf Apache VirtualHost file:

PassengerStartTimeout 300

Restart Apache and try the browser again.

Perhaps the most challenging part of configuring Canvas-LMS to work on a Red Hat system was maintaining patience. Arrangements would frequently fail, but only after watching the compiler pinwheel spin for a looooong time. Then it was back to the "A" positions with a slightly different sequence. The preceding is what Moser Consulting found worked, but your needs or hardware may be different. Don't be afraid to experiment; in Linux, there is always more than one way to skin a `cat /etc/passwd`. 

 

Good luck!

 

References:

      https://github.com/instructure/canvas-lms/wiki/Production-Start 

      https://www.cyberciti.biz/faq/install-and-setup-postgresql-on-rhel-8/ 

      https://www.postgresql.org/docs/current/auth-pg-hba-conf.html 

      https://bitnami.com/stack/canvaslms 

      https://www.tecmint.com/install-ruby-on-centos-rhel-8/ 

      https://www.netiq.com/documentation/identity-manager-47/setup_windows/data/connecting-to-a-remote-postgresql-database.html 

      https://linuxize.com/post/how-to-install-yarn-on-centos-8/ 

      https://www.phusionpassenger.com/docs/advanced_guides/install_and_upgrade/apache/install/oss/el8.html 

      https://www.linode.com/docs/guides/how-to-install-canvas-on-debian-10/ 

      https://computingforgeeks.com/install-node-js-14-on-centos-rhel-linux/ 

      https://www.ripplesoftware.ca/how-to-install-canvas-lms-on-ubuntu-18-4/