Manual installation
Source: docs/manual-install.md
This page documents, step by step, the manual procedure to install each of the two configurations exercised by the integration matrix:
| Option | Trac handler | Services |
|---|---|---|
| A | mod_wsgi |
trac + svn + git + gitExternal |
| B | mod_python |
trac |
These are exactly the steps hzforge install automates — written out so
you can run them by hand, audit what the tool does, or recover a host without it.
Everything runs as root on Rocky/RHEL 8 with Apache 2.4. Substitute your hub name
for <hub> (the directory /etc/httpd/<hub>.conf.d/ must already be included by the
vhost via IncludeOptional <hub>.conf.d/*.conf).
The two Trac interpreters cannot coexist — load exactly one of
mod_wsgi/mod_python. Switching handlers means disabling the other module and doing a fullsystemctl restart httpd(a graceful reload cannot swap an embedded interpreter).
See Requirements for the full host/network prerequisites.
Common prerequisites (both options)
1. Enable the package repositories
dnf -y install dnf-plugins-core epel-release
dnf config-manager --set-enabled powertools || dnf config-manager --set-enabled crb
cat >/etc/yum.repos.d/hubzero.repo <<'REPO'
[hubzero]
name=HUBzero
baseurl=http://packages.hubzero.org/rpm/julian-el8
enabled=1
gpgcheck=1
gpgkey=https://packages.hubzero.org/rpm/hubzero-rpm-key-pub-2025
REPO
rpm --import https://packages.hubzero.org/rpm/hubzero-rpm-key-pub-2025
2. Base packages
dnf -y install httpd httpd-devel mod_ssl \
python2 python2-pip python2-devel gcc redhat-rpm-config
httpd-devel + gcc are needed to pip-build mod_wsgi (Option A); python2/python2-pip
build the Py2 Trac stack used by both handlers.
3. The httpd runtime directory
httpd needs /run/httpd. With systemd this is created by systemd-tmpfiles; in a
container/chroot create it yourself before starting httpd:
install -d -m 0710 -o root -g apache /run/httpd
Option A — mod_wsgi: trac + svn + git + gitExternal
A1. Subversion source
hubzero-trac rpm-requires subversion-devel, and the svn service needs
subversion + mod_dav_svn. Pick one source.
AppStream (simplest):
dnf -y module enable subversion:1.10
WanDisco 1.10 (matches some production hubs):
cat >/etc/yum.repos.d/wandisco-svn110.repo <<'REPO'
[wandisco-svn110]
name=Wandisco SVN 1.10 RPM repository for Rocky 8
baseurl=http://opensource.wandisco.com/rhel/8/svn-1.10/RPMS/$basearch/
enabled=0
gpgcheck=1
gpgkey=http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco
priority=1
module_hotfixes=1
REPO
# if a subversion module stream is enabled, reset it so module_hotfixes wins:
dnf -y module reset subversion
With WanDisco, append --enablerepo=wandisco-svn110 to the subversion/mod_dav_svn
dnf install below. With AppStream, no --enablerepo is needed.
A2. Trac packages and the WSGI stack
# Trac plugins (pulls subversion-devel from the source enabled above)
dnf -y install hubzero-trac
# Trac + mod_wsgi from PyPI. umask 022 so root-built files are world-readable
# (apache must import them); the default umask 0077 would make Trac return 500.
umask 022
pip2 install 'Trac>=1.0,<1.1' # match envs at DB schema 26 (no upgrade)
pip2 install 'mod_wsgi==4.9.4' # last Python-2-capable mod_wsgi
A3. Directories
install -d -m 0755 -o apache -g apache /opt/trac
install -d -m 0750 -o apache -g apache /opt/trac/tools
install -d -m 0755 -o apache -g apache /opt/trac/wsgi
A4. The WSGI shim
/opt/trac/wsgi/hubtrac.wsgi re-splits the URL so any /opt/trac/tools/<name> env
is served generically (the mod_wsgi replacement for PythonOption TracUriRoot):
# /opt/trac/wsgi/hubtrac.wsgi (mode 0644, apache:apache)
import os, re
from trac.web.main import dispatch_request
TOOLS = '/opt/trac/tools'
PAT = re.compile(r'^(/tools/([^/]+))(/.*)?$')
def application(environ, start_response):
full = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
m = PAT.match(full)
if not m or not os.path.isdir(os.path.join(TOOLS, m.group(2))):
start_response('404 Not Found', [('Content-Type', 'text/plain')])
return ['No such Trac environment\n']
environ['trac.env_path'] = os.path.join(TOOLS, m.group(2))
environ['SCRIPT_NAME'] = m.group(1)
environ['PATH_INFO'] = m.group(3) or '/'
return dispatch_request(environ, start_response)
A5. Load mod_wsgi (and ensure mod_python is off)
The LoadModule/WSGIPythonHome lines come from the pip build:
mod_wsgi-express module-config
Write its output into /etc/httpd/conf.modules.d/10-wsgi.conf (mode 0644):
# Python2 mod_wsgi
LoadModule wsgi_module "/usr/lib64/python2.7/site-packages/mod_wsgi/server/mod_wsgi-py27.so"
WSGIPythonHome "/usr"
WSGISocketPrefix run/wsgi
WSGIRestrictEmbedded On
# the two interpreters can't coexist -- make sure mod_python is not loaded
[ -f /etc/httpd/conf.modules.d/10-python.conf ] && \
mv /etc/httpd/conf.modules.d/10-python.conf{,.disabled}
A6. The Trac drop-in
/etc/httpd/<hub>.conf.d/00-forge-trac.conf (mode 0640). The WSGIScriptAliasMatch
self-diverts at translate-name, so no rewrite carve-out is needed:
# HUBzero 'trac' service
RewriteEngine On
WSGIDaemonProcess trac user=apache group=apache processes=2 threads=15 python-home=/usr display-name=%{GROUP}
WSGIApplicationGroup %{GLOBAL}
WSGIScriptAliasMatch "^/tools/[^/]+(?=/(?:wiki|wiki_render|timeline|roadmap|browser|changeset|ticket|newticket|report|query|search|admin|prefs|login|logout|about|diff|attachment|raw-attachment|export|chrome|log|pygments)(?:/|$))" /opt/trac/wsgi/hubtrac.wsgi process-group=trac
<Directory /opt/trac/wsgi>
<Files hubtrac.wsgi>
Require all granted
</Files>
</Directory>
(Optional) to have Apache authenticate /login so Trac sees REMOTE_USER, append:
<LocationMatch "^/tools/[^/]+/login">
AuthType Basic
AuthBasicProvider ldap
AuthName "HUBzero Trac"
AuthLDAPURL ldap://127.0.0.1/ou=users,dc=hubzero,dc=org
AuthLDAPBindDN "cn=search,dc=hubzero,dc=org"
AuthLDAPBindPassword "..."
Require valid-user
</LocationMatch>
A7. svn service
# packages: subversion + mod_dav_svn (from the A1 source) + the Py2 SWIG bindings.
# add --enablerepo=wandisco-svn110 here if you chose WanDisco in A1.
dnf -y install subversion mod_dav_svn
dnf -y install subversion-python # from hubzero; also lights up Trac's repo browser
groupadd -f hzsvn
install -d -m 0755 -o apache -g apache /opt/svn
install -d -m 0750 -o apache -g apache /opt/svn/tools
install -d -m 0700 -o apache -g apache /etc/httpd/<hub>.conf.d/svn
/etc/httpd/<hub>.conf.d/00-forge-svn.conf (mode 0640). mod_dav_svn is a <Location>
handler, so it must be shielded from the CMS catch-all rewrite:
# HUBzero 'svn' service
RewriteEngine On
# DAV-svn is a <Location> handler -> shield from the CMS catch-all rewrite
RewriteRule "^/tools/[^/]+/svn(/|$)" - [END]
IncludeOptional /etc/httpd/<hub>.conf.d/svn/svn.conf
The per-tool <Location /tools/<name>/svn> blocks live in svn/svn.conf, which is
generated from MySQL by the hub's forge tooling — hzforge only includes it.
A8. git and gitExternal services
dnf -y install git
groupadd -f hzgit
install -d -m 0755 -o apache -g apache /opt/git /opt/git/tools
install -d -m 0755 -o apache -g apache /opt/gitExternal /opt/gitExternal/tools
install -d -m 0700 -o apache -g apache /etc/httpd/<hub>.conf.d/git
/etc/httpd/<hub>.conf.d/00-forge-git.conf (mode 0640):
# HUBzero 'git' service
RewriteEngine On
# git http-backend (ScriptAliasMatch self-diverts; shield non-protocol paths)
RewriteRule "^/tools/[^/]+/git(/|$)" - [END]
IncludeOptional /etc/httpd/<hub>.conf.d/git/git.conf
/etc/httpd/<hub>.conf.d/00-forge-gitExternal.conf (mode 0640):
# HUBzero 'gitExternal' service
RewriteEngine On
RewriteRule "^/tools/[^/]+/gitExternal(/|$)" - [END]
IncludeOptional /etc/httpd/<hub>.conf.d/git/gitExternal.conf
As with svn, the ScriptAliasMatch git-http-backend routes live in git/git.conf /
git/gitExternal.conf (MySQL-generated); the drop-ins only include them.
A9. Validate and start
apachectl configtest
systemctl restart httpd # full restart: the interpreter module set changed
# container/chroot without systemd: httpd -k start (or httpd -k restart)
Verify a throwaway env serves (the generic shim handles any env):
trac-admin /opt/trac/tools/probe initenv probe sqlite:db/trac.db
chown -R apache:apache /opt/trac/tools/probe
curl -sk https://<hub>/tools/probe/wiki | head # expect a Trac wiki page
rm -rf /opt/trac/tools/probe
Option B — mod_python: trac
The legacy in-process handler. Unlike mod_wsgi there is no generic route: each Trac
environment needs its own <Location> block, so the drop-in is regenerated whenever you
add or remove envs.
B1. Subversion source
Same as A1 — hubzero-trac still needs subversion-devel, so
enable a source even though you are not installing the svn service:
dnf -y module enable subversion:1.10 # (or set up the WanDisco repo as in A1)
B2. Trac packages and mod_python
dnf -y install hubzero-trac
umask 022
pip2 install 'Trac>=1.0,<1.1' # mod_python loads Trac from site-packages
# mod_python comes from the hubzero (julian-el8) repo, built for the Python 2.7 it embeds
dnf -y install mod_python
B3. Directories and egg cache
install -d -m 0755 -o apache -g apache /opt/trac
install -d -m 0750 -o apache -g apache /opt/trac/tools
install -d -m 0755 -o apache -g apache /opt/trac/.egg-cache
B4. Load mod_python (and ensure mod_wsgi is off)
cat >/etc/httpd/conf.modules.d/10-python.conf <<'EOF'
LoadModule python_module modules/mod_python.so
EOF
[ -f /etc/httpd/conf.modules.d/10-wsgi.conf ] && \
mv /etc/httpd/conf.modules.d/10-wsgi.conf{,.disabled}
B5. The Trac drop-in
/etc/httpd/<hub>.conf.d/00-forge-trac.conf (mode 0640). The verb paths are a
<Location> handler, so they are shielded from the CMS catch-all; then one
<Location> per env:
# HUBzero 'trac' service (mod_python)
RewriteEngine On
# Trac via mod_python -- <Location> per env; shield verbs from the CMS rewrite
RewriteRule "^/tools/[^/]+/(wiki|wiki_render|timeline|roadmap|browser|changeset|ticket|newticket|report|query|search|admin|prefs|login|logout|about|diff|attachment|raw-attachment|export|chrome|log|pygments)(/|$)" - [END]
PythonOption PYTHON_EGG_CACHE /opt/trac/.egg-cache
# repeat this block for every env under /opt/trac/tools/<env>/conf/trac.ini
<Location /tools/myproject>
SetHandler mod_python
PythonHandler trac.web.modpython_frontend
PythonInterpreter main_interpreter
PythonOption TracEnv /opt/trac/tools/myproject
PythonOption TracUriRoot /tools/myproject
</Location>
Because the route is per-env, you must rebuild this drop-in and reload httpd each time an env is created or removed (
hzforge repair tracdoes exactly this by enumerating/opt/trac/tools/*/conf/trac.ini).
B6. Validate and start
apachectl configtest
systemctl restart httpd # full restart: the interpreter module set changed
curl -sk https://<hub>/tools/myproject/wiki | head # expect a Trac wiki page
Doing it with hzforge instead
Each option above is a single command:
# Option A
sudo python3 hzforge.py install trac svn git gitExternal --svn-source appstream
# Option B
sudo python3 hzforge.py install trac --trac-handler mod_python --svn-source appstream
hzforge additionally decides restart-vs-reload from the running interpreter set, fixes
pip file permissions, creates /run/httpd on non-systemd hosts, and runs the
self-test. See Usage for the full command set and
Architecture for why the drop-ins are wired this way.