Fork me on GitHub

Running Zena on Servers

Prerequisites

Refer to Installing the Requirements to ensure that your webserver supports the options you want.

This page provides general instructions. Information specific to special circumstances or hosters may be added as sub-pages.

Database Setup

You will want to use a production database instead of sqlite. If your server has an admin interface to create a database, this step may be easy. Otherwise, refer the documentation for the database to install and configure it on your operating system.

The following steps assume that you have created a MySQL database named “zena_db”, on the host mysql.example.org, accessible for the database user “db_user_zena” with “secretpassword”.

Get Your Application on The Server

There are different methods to accomplish this.

Create a self-contained Rails-App

This is the easiest way to get zena installed on a webserver. It allows you to simply upload your local zena application to a webserver, without having to install zena on the server again and reconfiguring the GEM_PATH.

cd path/to/zena_app

# make a backup
cd ..
tar czf myapp.tgz zena_app
cd zena_app

# "freeze" all required gems (installs gems into vendor/gems subdir)
export RAILS_ENV=production
rake gems:unpack

Then copy the (now self-contained) application directory to the hosting server.

Alternatively, Install Zena on the Server

If you can not create a self-contained application, you need to install zena on the server. Follow the procedure in Getting Started.

Configure Your Application for Production

Configure the Database

Adapt the “production” settings in config/database.yml, for example:

production:
  adapter: mysql
  database: zena_db
#  host: mysql.example.org
#  port: 3306
#  we connect through a fast unix socket (database runs local)
  socket: /var/lib/mysql/mysql.sock
  username: db_user_zena
  password: secretpassword

and chmod go-rwx config/database.yml

Set up the Environment

Find out the gem path (execute this on the server):

gem env gempath

Edit “config/environment.rb” and add the following line at the top of the file (replacing THE_PATH with the proper gem path):

ENV['GEM_PATH'] ||= 'THE_PATH'
ENV['RAILS_ENV'] ||= 'production'

Set up log rotation

Otherwise, logs will grow forever and stop your server or account from working one unexpected day…

The best way seems to configure “logrotate” on the server.
http://stackoverflow.com/questions/4883891/ruby-on-rails-production-log-rotation

# use date as a suffix of the rotated file
dateext

/path/to/.../zena/app/log/production.log {
rotate 20
daily
missingok
compress
delaycompress
notifempty
copytruncate
}

Up to rails 3.0, rails itself could do log rotation to some degree, if you added the following to config/environments/production.rb:

config.logger = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log", 50, 1.megabyte)

Webserver Configuration

Required Settings


# Zena's Apache Configuration Requirements
#
# Add or rename this file to the .htaccess file in your
# public/ webroot. However, if you have the permission, you may prefer to
# add this to your central apache configuration (avoids to reload the
# rules each time the .htaccess is read). 

# The webroots of your domains need to point to the sites/<domain>/public directories,
# If using passenger you will have to point it to the application root.
#PassengerAppRoot /var/www/htdocs/web22/files/rails/app

# Zena requires the following options, but you may not be allowed to change them
# in the .htaccess file. Thus, they are commented out to avoid causing an error
# even though the setting may already be correct.
#
#Options +FollowSymLinks -MultiViews
#
# FollowSymLinks is requried to operate multiple sites with one installation, however
# FollowSymlinksIfOwnerMatch is also sufficient (prevents publishing
# other user's files).
# Without FollowSymlinks, the central assets and fs-skins can not be loaded,
# and you will only be able to use zena with the "single" brick enabled.

# For a working upload progress bar, you will need apache's mod-upload-progress
# and a configuration like this:
#
#  <Location />
#    # enable tracking uploads in /
#    TrackUploads On
#  </Location>
#
#  <Location /upload_progress>
#    # enable upload progress reports
#    ReportUploads On
#  </Location>

# You may enable this to deflate pages (compress data before sending to browser)
#AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css
#BrowserMatch ^Mozilla/4 gzip-only-text/html
#BrowserMatch ^Mozilla/4.0[678] no-gzip
#BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

# This would enables the execution of all .fcgi
# files as fastcgi dispatcher (not recomended).
#AddHandler fcgid-script .fcgi

RewriteEngine On

# Most of the time the filesystem path is not equal to the web path
# and this .httaccess file is located in the webroot, thus:

RewriteBase /

# .htaccess vs. central rewrite configuration
# http://stackoverflow.com/questions/286004/hidden-features-of-mod-rewrite
# The leading "/?" (optional '/') used in the rules should make them work in the
# .htaccess as well as in a central apache configuraton file context.
# The [L] (last) flag may not stop the processing in .htaccess files,
# then the most general rules actually need to come last.

# Directing

# Check for maintenance file and redirect all requests if present.
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]

# Conditional language selection for file cache (w/o Zena nor Multiviews).
#RewriteCond %{REQUEST_URI} ^/?$
# if browser wants "en" as first language
#RewriteCond %{HTTP:Accept-Language} ^en
#RewriteRule ^.*$ /en/ [R,L]

# Redirect to default language of website.
RewriteCond %{REQUEST_URI} ^/?$
RewriteRule ^.*$ /de/ [R,L]

# Caching

# A file caching workaround is required for every language and 
# custom base url subpage as well... :
# => NOT NEEDED if the cached files are created as regular /.../index.html files
#
# Silently rewrite /de/ to /de.html
RewriteCond %{REQUEST_URI} ^/?de/?$
# do not redirect qeries to the cached mainpage
RewriteCond %{QUERY_STRING} ^$
# we need to check if de.html exists, because zena does not recongnize it as a valid url
RewriteCond %{DOCUMENT_ROOT}/de.html -f
RewriteRule ^.*$ /de.html [L]

# Serve specific static (cached) asset versions for previews etc.
# (But mapping query hashes to file extensions does not work where 
# multiviews is used to negotiate extensions! The request fails 
# before any rewrite rules are processed)
# => NOT NEEDED use the version in file name scheme (name_v***.extension) instead,
# to deliver specific versions, and cache the current default
# public version (as name.extension) on disk without a hash in its filename!)
RewriteCond %{QUERY_STRING} ^[0-9]+$
RewriteCond %{DOCUMENT_ROOT}/public/cache/%{REQUEST_FILENAME}.%{QUERY_STRING} -f  
# We use '.', because if we use '?' apache won't find the file.
# (It is not globing, but looking just for the REQUEST_FILENAME,
# or a file with "?" in its name.)
RewriteRule ^/?(.*)$ /public/cache/%{REQUEST_FILENAME}.%{QUERY_STRING} [L]  

# Explicitly truncating query hashes from request for static CSS, JS.
# ONLY NEEDED for platforms where this is not the default (mod_passenger?).
# Rails (and zena?) changes the query hashes on asset updates to trigger clients to
# update their not yet expired browser cache.
#
RewriteCond %{QUERY_STRING} ^[0-9]+$
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} -f
RewriteRule ^/?(.*)$ /%{REQUEST_FILENAME} [L]

# Serve pages directly from zenas dynamic file cache, if possible.
# The empty query check breaks <r:cache allow_query='...' /> that should be
# mapped to files named like page12&year=2013.html
# => NOT NEEDED when cache is located in the webroot and multiviews is enabled.
RewriteCond %{QUERY_STRING} ^$
RewriteCond %{DOCUMENT_ROOT}/public/cache/%{REQUEST_FILENAME} -f [OR]
# apache fcgi without multiviews wont pick up .html
RewriteCond %{DOCUMENT_ROOT}/public/cache/%{REQUEST_FILENAME}.html -f  
RewriteRule ^/?(.*)$ /public/cache/%{REQUEST_FILENAME} [L]

# Adding .html extension if page is already cached.
# => NOT NEEDED when cache is located in the webroot and multiviews is enabled.
# last rule?
#
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^/?([^.]+?)/?$ /$1.html [QSA]

# Zena Invocation
# Invoking zena through (f)cgi (without mod_passenger) is implemented
# by letting requests execute/query the (f)cgi ruby dispatcher:

# * if no corresponding files are present in the webroot.

RewriteCond %{REQUEST_FILENAME} !-f

# * if no cached .html version exists
# MAY NOT BE NEEDED if multiview requests are handled before this rule is considered?
# The .html extensions are only added with multiviews enabled,
# but zena breaks multiviews negotiating by diverging from the dir/index.html scheme.
# Thus, if no rewrite to .html files is enabled, also comment this condition out, to let 
# zena handle the requests.

RewriteCond %{REQUEST_FILENAME}.html !-f 

# * if it is not a directory with an index.html

RewriteCond %{REQUEST_FILENAME}/index.html !-f
#[OR]

# *
#RewriteCond %{REQUEST_FILENAME} dispatch.fcgi$
#
# If your server uses a separate fastcgi directory add its path before /dispatch.fcgi:
RewriteRule ^/?(.*)$ /dispatch.fcgi [QSA,L]

# zena's addition of versioned queries to linked assets allows for long expiry
# periods, in the hope that clients honor expiration periods for queries.

# The IfModule clause is commented out, to notice when expires is not working properly.
#<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresDefault A0
    ExpiresByType image/jpeg "access plus 1 year" 
    ExpiresByType image/gif "access plus 1 year" 
    ExpiresByType image/png "access plus 1 year" 
    ExpiresByType text/css "access plus 1 year" 
    ExpiresByType application/javascript "access plus 1 year" 
    ExpiresByType text/javascript "access plus 1 year" 
#</IfModule>

# Disable execution of .php scripts that may get uploaded by users.
# (This might be overridden with .htaccess files in subdirectories!)
AddHandler default-handler php
RemoveType application/x-httpd-php .php
AddType application/x-httpd-php-source .php

If using mod_passenger (aka mod_rails or mod_rack)

Set the PassengerAppRoot. to whatever your APP-ROOT is.

If you can not customize your passenger setup (AllowOverride Options is forbidden), you may symlink the APP-ROOT/sites/<domain>/public directories to APP-ROOT/<domain> and point the webroot to these.

If FollowSymlinks is disallowed, you may enable the “single” brick to let zena use only the standard APP-ROOT/public setup. However, with the single brick enabled you will of course need to create a separate zena-app and database for every site you want to host.

If using FastCGI

Create a dispatch.fcgi file in your fastcgi directory or every webroot (depends on your server configuration) with the following content (using your own APP-ROOT):

#!/usr/bin/env ruby
require "APP-ROOT/config/environment" 
require 'fcgi_handler'
RailsFCGIHandler.process!

Webroot

In your webserver’s configuration or admin interface, set the document root for your domain to APP-ROOT/sites/<domain>/public, or symlink this folder to the domain’s DocumentRoot:

ln -s APP-ROOT/sites/<domain>/public /var/www/path/to/webroot

If your webserver does not follow symlinks (also used to reach zena images and stylesheets etc.), you will have to move the whole application into the webroot directory, enable the “single” brick, use APP-ROOT/public as the document root, and take care to forbid all access except to the APP-ROOT/public/ directory. However, with the single brick enabled you will of course need to create a separate zena-app and database for every site you want to host.

Final note

This is a basic setup without some of the advanced features of zena. The installation should let you get started but there are still some things you should look for and check out:

  • upload progress (used to need a drb background worker)
  • sphinx search indexing
  • ssl on auth support
  • pdf generation brick
  • Latex (math) generation
  • image library
  • ...

Have fun and update this documentation wiki, if you find out something new.