In the quest for a project/issue tracker I narrowed my choices down to JIRA, Redmine, and YouTrack. I opted for JIRA as I’ve had some experience with some of Atlassian’s other products and figured it was the best place to start.

It does not exist in the FreeBSD ports tree, and Atlassian’s website does not mention FreeBSD, only Linux and Solaris. So we’re left to installing it on our own.

Atlassian does provide a WAR, but their instructions say the WAR distribution will end in the next major version (7.0, as of the writing of this). It also requires you to make a bunch of modifications to Tomcat to match their own kooky distribution, so we shall press on to their preferred option.

You can download the .tar.gz version from Atlassian’s website.

We’re going to need OpenJDK and tomcat-native, so a quick pkg install openjdk tomcat-native will install the software we require. tomcat-native is not the full Tomcat server, it just contains the portions to interact with the Apache Portable Runtime in an attempt to speed up Tomcat. The JIRA package contains their own modified version of Tomcat so we don’t need to duplicate their work.

At this point we’ve got most of the things required to get this setup. If you want to use a database more robust than the built-in HSQL you can check the list of supported databases. In my case I chose PostgreSQL, so pkg install postgresql94-server and we’re off.

We’ll want to create a user for JIRA

adduser

Username: jira
Full name: JIRA psuedo-user
Uid (Leave empty for default): 71
Login group [jira]: 
Login group is jira. Invite jira into other groups? []: 
Login class [default]: 
Shell (sh csh tcsh zsh rzsh git-shell nologin) [sh]: 
Home directory [/home/jira]: /usr/local/jira
Home directory permissions (Leave empty for default): 
Use password-based authentication? [yes]: no
Lock out the account after creation? [no]: no
Username   : jira
Password   : <disabled>
Full Name  : JIRA psuedo-user
Uid        : 71
Class      : 
Groups     : jira 
Home       : /usr/local/jira
Home Mode  : 
Shell      : /bin/sh
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (jira) to the user database.

Database Configuration

The next bit is setting up the database, so you can skip this if you’re using something other than PostgreSQL or you have it setup already.

Add the following to your /etc/rc.conf

postgres_enable="YES"

We need PostgreSQL to do the initial database setup, which can be done via /usr/local/etc/rc.d/postgresql initdb and we’ll want to start it up with /usr/local/etc/rc.d/postgresql start. Alternatively you can also use the service command for both commands in service postgresql initdb and service postgresql start.

We’ll also need to create a user for JIRA in PostgreSQL, either switch to the pgsql user with su pgsql and connect to the database with psql postgres or use psql --username pgsql postgres. The results are the same.

psql (9.4.0)
Type "help" for help.

postgres=# CREATE USER jira WITH PASSWORD 'jira_password';

postgres=# CREATE DATABASE jiradb;

postgres=# GRANT ALL PRIVILEGES ON DATABASE jiradb TO jira;

postgres=# \q

Replace with a more appropriate password.

JIRA Setup

Most of this is paraphrasing the instructions from here.

We’ll need to extract the .tar.gz we downloaded from Atlassian, so tar xzvf atlassian-jira-*.tar.gz, this will create a atlassian-jira-$version-standalone directory.

You need to decide where you’re going to store JIRA, I chose /usr/local/share/jira for the installation directory and /usr/local/jira for JIRA_HOME. The installation directory is where your version of JIRA will live, but it won’t contain any of your information to make it easier to upgrade. Your JIRA_HOME directory is where your stuff will reside.

mv atlassian-jira-6.3.14-standalone /usr/local/share/jira
mkdir /usr/local/jira

We want the JIRA user to be able to write to all of the JIRA_HOME, but only logs, temp, and work in the installation directory.

cd /usr/local/share/jira
chown jira:jira logs temp work

I’ve adapted an rc.d script for JIRA based on this confluence script and the PostgreSQL rc script.

#!/bin/sh

#
# PROVIDE: jira
# REQUIRE: DAEMON 
# KEYWORD: shutdown

. /etc/rc.subr

load_rc_config jira

JAVA_HOME="/usr/local/openjdk7"
JRE_HOME="/usr/local/openjdk7/jre"
JIRA_INSTALL="/usr/local/share/jira"
JIRA_HOME="/usr/local/jira"

jira_enable=${jira_enable:-"NO"}
jira_user=${jira_user:-"jira"}

name=jira
rcvar=jira_enable

procname="java"
pidfile="/var/run/jira.pid"

start_cmd="jira_start"
stop_cmd="jira_stop"

jira_start()
{
 su -l ${jira_user} -c "export JAVA_HOME=${JAVA_HOME};export JRE_HOME=${JRE_HOME};export JIRA_HOME=${JIRA_HOME};${JIRA_INSTALL}/bin/catalina.sh start || err 1 'Error triggering JIRA startup'"
}

jira_stop()
{
 su -l ${jira_user} -c "export JAVA_HOME=${JAVA_HOME};export JRE_HOME=${JRE_HOME};export JIRA_HOME=${JIRA_HOME};${JIRA_INSTALL}/bin/catalina.sh stop 10 -force || err 1 'Error triggering JIRA shutdown'"
}

run_rc_command "$1"

Change your directories as required.

Save that to /usr/local/etc/rc.d/jira and we’ll need to make it executable with `

chmod u-w,ugo+x /usr/local/etc/rc.d/jira

To get it to start on boot, add

jira_enable="YES"

to /etc/rc.conf you may also wish to add postgresql to the require line to ensure JIRA is started after PostgreSQL:

# REQUIRE: DAEMON postgresql

We can now start JIRA with /usr/local/etc/rc.d/jira start or service jira start

At this point you should be able to point your browser at http://jira-server-name:8080 and go through the JIRA setup wizard to configure the database settings.

nginx proxy

I’m currently serving my JIRA setup behind a nginx proxy, which is relatively easy to setup. I’m using SSL and to make JIRA play nice you have to follow these instructions which are Apache specific after a certain part.

If you are planning on having JIRA available on a path like example.com/jira you’ll need to edit /usr/local/share/jira/conf/server.xml and change the path variable.

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
  <Context path="/jira" docBase="${catalina.home}/atlassian-jira" reloadable="false" useHttpOnly="true">
    <!--
    ====================================================================================

     Note, you no longer configure your database driver or connection parameters here.
     These are configured through the UI during application setup.

    ====================================================================================
    -->

    <Resource name="UserTransaction" auth="Container" type="javax.transaction.UserTransaction"
              factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>
    <Manager pathname=""/>
  </Context>
</Host>

In my case I am using a subdomain so I did not change it from the default empty string.

We also need to enable a connector to be used by the proxy. We’ll leave the existing connector that listens on port 8080, and add one that listens on 8443 that is told it will be served over HTTPS.

<Service name="Catalina">
  <Connector port="8080"

    maxThreads="150"
    minSpareThreads="25"
    connectionTimeout="20000"

    enableLookups="false"
    maxHttpHeaderSize="8192"
    protocol="HTTP/1.1"
    useBodyEncodingForURI="true"
    redirectPort="8443"
    acceptCount="100"
    disableUploadTimeout="true"/>

  <Connector port="8443"

    maxThreads="150"
    minSpareThreads="25"
    connectionTimeout="20000"

    enableLookups="false"
    maxHttpHeaderSize="8192"
    protocol="HTTP/1.1"
    useBodyEncodingForURI="true"
    redirectPort="8443"
    acceptCount="100"
    disableUploadTimeout="true"

    scheme="https"
    proxyName="jira.example.com" 
    proxyPort="443" 
    secure="true"/>

To quote Atlassian

The proxyName parameter should be set to the FQDN that the JIRA server will be accessed on - for example jira.example.com if accessing it on http://jira.example.com or example.com if accessing it on http://www.example.com/jira. It should not include the context path.

That’s all we need to do on the JIRA / tomcat side of things, we just need to add a section to our nginx configuration.

server {

    access_log  /var/log/nginx/jira/access.log main;
    error_log   /var/log/nginx/jira/error.log info;

    listen      127.0.0.1:443 ssl spdy; 
    server_name  jira.example.com;

    location / {

        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:8443/;
    }
}

Restart nginx and JIRA and you should be good to go. You’ll also want to disable JIRA’s built-in gzip compression.

Set Use gzip compression to OFF as in Configuring JIRA Options. GZIP compression is known to cause performance issues using a reverse-proxy, especially if the proxy is also compressing the traffic.