선행조건

시스템에 Subversion이 설치되어 있어야 한다. Redhat, CentOS등 OS 설치 CD에 기본 포함되어 있다.

개요

Trac 프로젝트 홈페이지

다음 내용은 프로젝트 홈페이지의 내용을 일부 발췌 번역한 것이다.

Trac은 소프트웨어 개발 프로젝트들을 위한 향상된 위키와 이슈 트랙킹 시스템이다. Trac 은 웹 기반 소프트웨어 프로젝트 관리를 위한 최소한의 접근법을 사용한다. 우리의 목표는 개발자들이 프로젝트 관리 방법에 신경쓰지 않고도 멋진 소프트웨어를 작성할 수 있도록 돕고자 하는 것이다. Trac은 정형화된 팀 개발 절차와 정책을 가능한 최소한으로 부여하고자 하였다.

Trac은 Subversion과의 연계, 통합된 위키 그리고 편의를 위한 리포팅 기능들을 제공한다.

Trac은 이슈 Description과 Commit 메시지들, 링크의 생성 뿐만 아니라 버그, 태스크, Changeset, 파일, 위키 페이지들 사이의 일관된 참조(seamless references-IT상에서의 Seamless의 의미)생성에 wiki 구문이 사용될 수 있도록 하였다. Timeline은 모든 프로젝트 이벤트들을 순서대로 보여줌으로서, 프로젝트의 전체 상황을 얻을 수 있고, 쉽게 진행 사항을 추적할 수 있도록 해준다.

설치

소스를 통한 설치 예

Source로 설치시 : trac v0.8.4 trac_0.8.4_pkgs_source.tar.gz

 
# // clearsilver-0.10.3 설치
[root@Hostname clearsilver-0.10.3]# ./configure --with-python=/usr/bin/python
[root@Hostname clearsilver-0.10.3]# make && make install
# // sqlite-2.8.17 설치
[root@Hostname sqlite-2.8.17]# ./configure
[root@Hostname sqlite-2.8.17]# make && make install
# // pysqlite 설치
[root@Hostname pysqlite]# python ./setup.py install
# // trac 설치
[root@Hostname trac-0.8.4]# python ./setup.py install
# // cjkcodecs-1.1.1 설치
[root@Hostname cjkcodecs-1.1.1]# python ./setup.py install
# // trac 한글화 패키지 설치
[root@Hostname trac]# cp trac-ktdms-041222.tar.gz /usr/share/trac
[root@Hostname trac]# cd /usr/share/trac
[root@Hostname /usr/share/trac]# tar zxvf trac-ktdms-041222.tar.gz

  • 혹시 Trac v0.8.4를 설치하였을 경우 나중에 웹에서 프로젝트 로드시 (ㅠ.ㅠ 파일 이름이 기억안난다. 새로 설치시 이문제가 발생하면 이문서의 파일명을 수정해주길 부탁드린다), 다음과 같은 에러가 발생할 수 있다.
    httpd.conf 에서 재 실행중 Perl 과 관련하여 parsing 에러가 나고 이를 야기한 파일명으로 xxx.pm을 알리는 에러메시지가 나온다면 정상 설치된 server의 해당 pm을 참고하여 빠진 내용을 추가한다.

RPM을 통한 설치 예

 
# rpm -Uvh *.rpm

  1. clearsilver-0.10.4-2.el5.rf.i386.rpm
  2. python-clearsilver-0.10.4-2.el5.rf.i386.rpm
  3. SilverCity-0.9.7-1.el5.i386.rpm
  4. python-docutils-0.4-1.el5.noarch.rpm
  5. trac-ko-0.10.4-1.el5.noarch.rpm

설정

All Version

디렉토리 설정

  • 설치가 정상적으로 완료되었다면 웹 서비스를 위한 추가적인 디렉토리 설정이 필요하다.
  • /usr/share/trac 폴더를 보면 모든 필요한 파일들이 설치되어 있다. 이들중 웹 서비스 폴더로의 이동이 필요한 것은 cgi-bin에 있는 cgi파일뿐이다.
  • 나머지는 trac 운영에 필요한 내부 리소스들이다. trac-amdin을 통해 프로젝트 웹 페이지 생성시 필요한 Template와 추가적인 plugin을 설치할 수 있는 디렉토리, 환경 설정등등을 포함하고 있다.
  • 먼저 trac.cgi을 웹서버의 cgi-bin 폴더로 이동하자. 웹에서 엑세스 할 수 있도록 적당한 권한을 설정한다.
 
[root@ProjectS www]# cd /var/www
[root@ProjectS www]# cp /usr/share/trac/cgi-bin/trac.cgi /var/www/cgi-bin/
[root@ProjectS www]# cd /var/www/cgi-bin
[root@ProjectS cgi-bin]# chmod 755 trac.cgi
[root@ProjectS cgi-bin]# chown apache.apache trac.cgi
[root@ProjectS cgi-bin]# chcon -t httpd_sys_script_exec_t trac.cgi
[root@ProjectS cgi-bin]# ls -lZ
-rwxr-xr-x apache apache user_u:object_r:httpd_sys_script_exec_t trac.cgi
[root@ProjectS cgi-bin]#

  • trac.cgi를 수행되도록 하기 위해서는 추가적인 Apache configuration 점검이 필요하다.
  • /etc/httpd/conf/httpd.conf의 다음 옵션이 활성화 되어 있는지 확인하자
AddHandler cgi-script .cgi

아파치의 Rewrite Engine을 사용한 웹 디렉토리 설정

  • Rewrite Engine의 사용과 /var/www/html이 아닌 다른곳(/home/wwwtrac)에 위치한데 따른 장단점
    1. 실제 리소스가 위치한 디렉토리의 노출을 막을 수 있다.
    2. Document Root 디렉토리 이하에 위치할 경우의 허락된 경로외의 절대 경로를 통해 들어올 수 있는 위험을 방지할 수 있다.
    3. Document Root 디렉토리의 속성을 상속받지 못함으로 필요한 옵션이 있으면 별도로 지정해 주어야 한다.
  • Rewrite Engine 사용을 위한 httpd.conf를 확인하자.
 
[root@Hostname /var/www]# cd /etc/httpd/conf/
[root@Hostname /etc/httpd/conf]# vi httpd.conf

# Rewrite Module이 다음과 같이 로드되었는지 체크하고 없다면 추가해준다.
LoadModule rewrite_module modules/mod_rewrite.so
  • 웹에서 사용자가 https://domain/proj/프로젝트명 으로 프로젝트에 접근할 수 있도록 설정해 주자.
    • 보안을 위해 HTTPS로의 접근만을 허용할 것이다.
 
[root@Hostname conf]# cd /etc/httpd/conf.d/
[root@Hostname /etc/httpd/conf.d]# vi ssl.conf

# =============> for Trac start
# Rewrite Engine을 활성화 시킨다.
RewriteEngine on
 
# Rewrite Rule을 사용하여 proj, projenv로의 접근 규칙을 정의한다.
RewriteRule ^/proj/+$                   /home/wwwtrac/proj/index.cgi [L]
RewriteCond /home/wwwtrac/projenv/$1    -d
RewriteRule ^/proj/([[:alnum:]]+)(/?.*) /var/www/cgi-bin/trac.cgi$2 [S=1,E=TRAC_ENV:/home/wwwtrac/projenv/$1]
RewriteRule ^/proj/(.*)                 /home/wwwtrac/proj/index.cgi
# <============= end
#
  • 최근 수정된 내용
# ----> set rewrite rule for project resources
RewriteEngine on
RewriteRule ^/proj/+$                   /home/wwwtrac/proj/index.cgi [L]
RewriteCond /home/wwwtrac/projenv/$1    -d
RewriteRule ^/proj/([[:alnum:]]+)(/?.*) /var/www/cgi-bin/trac.cgi$2 [S=1,E=TRAC_ENV:/home/wwwtrac/projenv/$1]
# /proj/ 에 존재하는 다른 문서들도 로드 가능하도록 RewriteRule 주석, 없는 문서 지정시 404에러 발생
##RewriteRule ^/proj/(.*)                        /home/wwwtrac/proj/index.cgi
# 이하 3줄은 http에 허가된 URL들이 https를 통해 로드되는 것을 막기위해 설정
RewriteRule ^/+$                        /home/wwwtrac/proj/index.cgi [L]
RewriteRule ^/blog/(.*)                 /home/wwwtrac/proj/index.cgi
RewriteRule ^/wiki/(.*)                 /home/wwwtrac/proj/index.cgi
 
# /proj/ 란 디렉토리로의 직접 접근 필요성이 없다면 필요없는 부분임
Alias /proj/ "/home/wwwtrac/proj/"
# ----> for running /home/wwwtrac/proj/index.cgi
<Directory "/home/wwwtrac/proj">
    SSLOptions +StdEnvVars
    Options Indexes FollowSymLinks ExecCGI
    PASSEnv LD_LIBRARY_PATH
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>
# <============= end
  • Plug-In을 위한 추가수정
RewriteRule ^/proj/([[:alnum:]]+)(/?.*) /var/www/cgi-bin/trac.fcgi$2 [S=1,E=TRAC_ENV:/home/wwwtrac/projenv/$1,E=PYTHON_EGG_CACHE:/home/wwwtrac/projenv/$1/htdocs]
  • 웹에서 proj, projenv로 접근 가능하도록 설정되었다.
  • 실제 해당 리소스들이 위치할 폴더를 /home에 만들어 주자.
 
[root@Hostname /etc/httpd/conf.d]# cd /home
[root@Hostname /home]# mkdir wwwtrac
[root@Hostname /home]# mkdir wwwtrac/proj
[root@Hostname /home]# mkdir wwwtrac/projenv
[root@Hostname /home]# chown -R apache.apache wwwtrac # 웹 서버 계정으로 소유자 변경
[root@Hostname /home]# chcon -R -t httpd_sys_content_t wwwtrac # for SELinux

  • wwwtrac : Trac 웹 리소스 root 디렉토리
  • proj : 사용자가 접근할 프로젝트 명들을 지정하기 위한 폴더 (https://domain/proj/프로젝트명)
  • projenv : 실제 Project 웹문서 리소스 및 SVN 프로젝트 정보가 저장될 디렉토리

V 0.8.4

Python 동적 라이브러리 모듈 로드를 위한 Apache LD_LIBRARY_PATH 설정 , v0.8.4?

  • Apache 데몬 실행시 LD_LIBRARY_PATH 환경 변수가 설정되도록 디폴트 profile(/etc/profile)에 LD_LIBRARY_PATH를 추가한다.
LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib:./:
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE INPUTRC LD_LIBRARY_PATH
  • Apache 데몬에서 모든 도큐멘트에 LD_LIBRARY_PATH 환경변수가 넘어갈 수 있도록 /etc/httpd/conf/httpd.conf를 수정한다.
<Directory "/var/www/html">
 
#
# Possible values for the Options directive are "None", "All",
# or any combination of:
#   Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
#
# Note that "MultiViews" must be named *explicitly* --- "Options All"
# doesn't give it to you.
#
# The Options directive is both complicated and important.  Please see
# http://httpd.apache.org/docs/2.2/mod/core.html#options
# for more information.
#
    Options Indexes FollowSymLinks ExecCGI
    # /var/www/html 이하 모든 문서에 LD_LIBRARY_PATH 환경 변수가 넘어갈 수 있도록 설정해 준다.
    PASSEnv LD_LIBRARY_PATH
 
#
# AllowOverride controls what directives may be placed in .htaccess files.
# It can be "All", "None", or any combination of the keywords:
#   Options FileInfo AuthConfig Limit
#
    AllowOverride None
 
#
# Controls who can get stuff from this server.
#
    Order allow,deny
    Allow from all
 
</Directory>
  • CGI script들에도 넘어가도록 설정해 주자.
<Directory "/var/www/cgi-bin">
    AllowOverride None
    Options ExecCGI
    PassEnv LD_LIBRARY_PATH
    Order allow,deny
    Allow from all
</Directory>
  • 현재 사용자의 환경변수에 LD_LIBRARY_PATH가 설정되어 있어도 apache 계정으로 실행하면서 profile 설정이 무시되는 듯 하다. 따라서, profile에 LD_LIBRARY_PATH를 넣어두는 것도 좋지만 확실한 방법은 httpd 데몬 start 스크립트(/etc/init.d/httpd)에 LD_LIBRARY_PATH를 export 시키는 것이 가장 확실한 방법으로 보인다. 물론 httpd.conf에 PassEnv가 설정되어 있어야 하는 것은 필수이다.
55: # The semantics of these two functions differ from the way apachectl does
56: # things -- attempting to start while running is a failure, and shutdown
57: # when not running is also a failure.  So we just do it the way init scripts
58: # are expected to behave here.
59: start() {
60:         export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib:
61:         echo -n $"Starting $prog: "
62:         check13 || exit 1
63:         LANG=$HTTPD_LANG daemon $httpd $OPTIONS
64:         RETVAL=$?
65:         echo
66:         [ $RETVAL = 0 ] && touch ${lockfile}
67:         return $RETVAL
68: }

wwwtrac/projenv/프로젝트명/conf/trac.ini를 위한 Predefined Configuration

  • 다음으로 trac의 환경파일을 설치된 해당 서버에 맞도록 커스터마이징 하기를 원하면 다음과 같이 할 수 있다. /usr/lib/python2.2/site-packages/trac/db_default.py 파일 수정 (원본과 비교하여 차이점을 해당 서버에 맞도록 수정)
default_config = \
 (('trac', 'htdocs_location', '/trac/'),
  ('trac', 'repository_dir', '/var/svn/myrep'),
  ('trac', 'templates_dir', '/usr/lib/trac/templates'),
  ('trac', 'database', 'sqlite:db/trac.db'),
  ('trac', 'default_charset', 'euc-kr'),
  ('logging', 'log_type', 'none'),
  ('logging', 'log_file', 'trac.log'),
  ('logging', 'log_level', 'DEBUG'),
  ('project', 'name', 'H/W Project'),
  ('project', 'descr', 'the H/W team Project'),
  ('project', 'url', 'http://192.168.1.11/'),
  ('project', 'icon', 'trac.ico'),
  ('project', 'footer',
   ' Visit the Codeforum Projects at<br />'
   '<a href="http://192.168.1.11/">Project Server</a>'),
  ('ticket', 'default_version', ''),
  ('ticket', 'default_severity', 'normal'),
  ('ticket', 'default_priority', 'normal'),
  ('ticket', 'default_milestone', ''),
  ('ticket', 'default_component', 'component1'),
  ('header_logo', 'link', 'http://192.168.1.11/'),
  ('header_logo', 'src', 'codeforum.gif'),
  ('header_logo', 'alt', 'H/W team Proejcts'),
  ('header_logo', 'width', '180'),
  ('header_logo', 'height', '50'),
  ('attachment', 'max_size', '1262144000'),
  ('diff', 'tab_width', '8'),
  ('mimeviewer', 'enscript_path', 'enscript'),
  ('notification', 'smtp_enabled', 'false'),
  ('notification', 'smtp_server', 'localhost'),
  ('notification', 'smtp_always_cc', ''),
  ('notification', 'always_notify_reporter', 'false'),
  ('notification', 'smtp_from', 'trac@localhost'),
  ('notification', 'smtp_replyto', 'trac@localhost'),
  ('timeline', 'changeset_show_files', '0'))

v 0.10.4

wwwtrac/projenv/프로젝트명/conf/trac.ini를 위한 Predefined Configuration

  • /usr/lib/python2.4/site-packages/trac/env.py 수정
    • 76 라인 project, descr 를 시스템에 맞도록 수정하자.
    • 79 라인 project, url 수정
    • 82 라인 project, footer 수정
    • 91 라인 logging, log_type 필요하면 file 또는 syslog 등등으로 수정
    • 99 라인 logging, log_level 필요하면 수정
 53: class Environment(Component, ComponentManager):
 54:     """Trac stores project information in a Trac environment.
 55:
 56:     A Trac environment consists of a directory structure containing among other
 57:     things:
 58:      * a configuration file.
 59:      * an SQLite database (stores tickets, wiki pages...)
 60:      * Project specific templates and wiki macros.
 61:      * wiki and ticket attachments.
 62:     """
 63:     setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)
 64:
 65:     base_url = Option('trac', 'base_url', '',
 66:         """Base URL of the Trac deployment.
 67:
 68:         In most configurations, Trac will automatically reconstruct the URL
 69:         that is used to access it automatically. However, in more complex
 70:         setups, usually involving running Trac behind a HTTP proxy, you may
 71:         need to use this option to force Trac to use the correct URL.""")
 72:
 73:     project_name = Option('project', 'name', 'My Project',
 74:         """Name of the project.""")
 75:
 76:     project_description = Option('project', 'descr', 'codeforum project',
 77:         """Short description of the project.""")
 78:
 79:     project_url = Option('project', 'url', 'https://10.5.3.11/proj/',
 80:         """URL of the main project web site.""")
 81:
 82:     project_footer = Option('project', 'footer',
 83:                             'Visit the Codeforum Projectes at<br />'
 84:                             '<a href="http://10.5.3.11/proj/">'
 85:                             'http://10.5.3.11/proj/</a>',
 86:         """Page footer text (right-aligned).""")
 87:
 88:     project_icon = Option('project', 'icon', 'common/trac.ico',
 89:         """URL of the icon of the project.""")
 90:
 91:     log_type = Option('logging', 'log_type', 'file',
 92:         """Logging facility to use.
 93:
 94:         Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""")
 95:
 96:     log_file = Option('logging', 'log_file', 'trac.log',
 97:         """If `log_type` is `file`, this should be a path to the log-file.""")
 98:
 99:     log_level = Option('logging', 'log_level', 'INFO',
100:         """Level of verbosity in log.
101:
102:         Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")
103:
104:     log_format = Option('logging', 'log_format', None,
105:         """Custom logging format.
106:
107:         If nothing is set, the following will be used:
108:
109:         Trac[$(module)s] $(levelname)s: $(message)s
110:
111:         In addition to regular key names supported by the Python logger library
112:         library (see http://docs.python.org/lib/node422.html), one could use:
113:          - $(path)s     the path for the current environment
114:          - $(basename)s the last path component of the current environment
115:          - $(project)s  the project name
116:
117:          Note the usage of `$(...)s` instead of `%(...)s` as the latter form
118:          would be interpreted by the ConfigParser itself.
119:
120:          Example:
121:          ($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s
122:
123:          (since 0.11)""")
  • /usr/lib/python2.4/site-packages/trac/web/chrome.py 수정
    • 라인 146 header_logo, link 수정
    • 라인 149 header_logo, src 수정(로고를 수정하고자 할 경우) 파일 실제 위치는 /var/share/trac/htdocs에 위치해야 한다.
121: class Chrome(Component):
122:     """Responsible for assembling the web site chrome, i.e. everything that
123:     is not actual page content.
124:     """
125:     implements(IEnvironmentSetupParticipant, IRequestHandler, ITemplateProvider,
126:                IWikiSyntaxProvider)
127:
128:     navigation_contributors = ExtensionPoint(INavigationContributor)
129:     template_providers = ExtensionPoint(ITemplateProvider)
130:
131:     templates_dir = Option('trac', 'templates_dir', default_dir('templates'),
132:         """Path to the !ClearSilver templates.""")
133:
134:     htdocs_location = Option('trac', 'htdocs_location', '',
135:         """Base URL of the core static resources.""")
136:
137:     metanav_order = ListOption('trac', 'metanav',
138:                                'login,logout,settings,help,about', doc=
139:         """List of items IDs to display in the navigation bar `metanav`.""")
140:
141:     mainnav_order = ListOption('trac', 'mainnav',
142:                                'wiki,timeline,roadmap,browser,tickets,'
143:                                'newticket,search', doc=
144:         """List of item IDs to display in the navigation bar `mainnav`.""")
145:
146:     logo_link = Option('header_logo', 'link', 'http://10.5.3.11/proj/',
147:         """URL to link to from header logo.""")
148:
149:     logo_src = Option('header_logo', 'src', 'common/codeforum.png',
150:         """URL of the image to use as header logo.""")
151:
152:     logo_alt = Option('header_logo', 'alt', '',
153:         """Alternative text for the header logo.""")
154:
155:     logo_width = IntOption('header_logo', 'width', -1,
156:         """Width of the header logo image in pixels.""")
157:
158:     logo_height = IntOption('header_logo', 'height', -1,
159:         """Height of the header logo image in pixels.""")
  • /usr/lib/python2.4/site-packages/trac/attachment.py 수정
    • 라인 289 attachment, max_size 수정
279: class AttachmentModule(Component):
280:
281:     implements(IEnvironmentSetupParticipant, IRequestHandler,
282:                INavigationContributor, IWikiSyntaxProvider)
283:
284:     change_listeners = ExtensionPoint(IAttachmentChangeListener)
285:     manipulators = ExtensionPoint(IAttachmentManipulator)
286:
287:     CHUNK_SIZE = 4096
288:
289:     max_size = IntOption('attachment', 'max_size', 209715200,
290:         """Maximum allowed file size for ticket and wiki attachments.""")
  • /usr/lib/python2.4/site-packages/trac/notification.py 수정
    • 라인 33 notification, smtp_enabled 수정
    • 라인 36 notification, smtp_server 수정
    • 라인 42 notification, smtp_user 수정
    • 라인 45 notification, smtp_password 수정
    • 라인 48 notification, smtp_from 수정
    • 라인 51 notification, smtp_replyto 수정
    • 라인 62 notification, smtp_default_domain 수정
31: class NotificationSystem(Component):
32:
33:     smtp_enabled = BoolOption('notification', 'smtp_enabled', 'true',
34:         """Enable SMTP (email) notification.""")
35:
36:     smtp_server = Option('notification', 'smtp_server', 'mail.codeforum.net',
37:         """SMTP server hostname to use for email notifications.""")
38:
39:     smtp_port = IntOption('notification', 'smtp_port', 25,
40:         """SMTP server port to use for email notification.""")
41:
42:     smtp_user = Option('notification', 'smtp_user', 'user',
43:         """Username for SMTP server. (''since 0.9'').""")
44:
45:     smtp_password = Option('notification', 'smtp_password', 'password',
46:         """Password for SMTP server. (''since 0.9'').""")
47:
48:     smtp_from = Option('notification', 'smtp_from', 'user@codeforum.net',
49:         """Sender address to use in notification emails.""")
50:
51:     smtp_replyto = Option('notification', 'smtp_replyto', 'user@codeforum.net',
52:         """Reply-To address to use in notification emails.""")
53:
54:     smtp_always_cc = Option('notification', 'smtp_always_cc', '',
55:         """Email address(es) to always send notifications to,
56:            addresses can be see by all recipients (Cc:).""")
57:
58:     smtp_always_bcc = Option('notification', 'smtp_always_bcc', '',
59:         """Email address(es) to always send notifications to,
60:            addresses do not appear publicly (Bcc:). (''since 0.10'').""")
61:
62:     smtp_default_domain = Option('notification', 'smtp_default_domain', 'codeforum.net',
63:         """Default host/domain to append to address that do not specify one""")
64:
65:     mime_encoding = Option('notification', 'mime_encoding', 'base64',
66:         """Specifies the MIME encoding scheme for emails.
67:
68:         Valid options are 'base64' for Base64 encoding, 'qp' for
69:         Quoted-Printable, and 'none' for no encoding. Note that the no encoding
70:         means that non-ASCII characters in text are going to cause problems
71:         with notifications (''since 0.10'').""")
72:
73:     use_public_cc = BoolOption('notification', 'use_public_cc', 'false',
74:         """Recipients can see email addresses of other CC'ed recipients.
75:
76:         If this option is disabled (the default), recipients are put on BCC
77:         (''since 0.10'').""")
78:
79:     use_short_addr = BoolOption('notification', 'use_short_addr', 'false',
80:         """Permit email address without a host/domain (i.e. username only)
81:
82:         The SMTP server should accept those addresses, and either append
83:         a FQDN or use local delivery (''since 0.10'').""")
84:
85:     use_tls = BoolOption('notification', 'use_tls', 'false',
86:         """Use SSL/TLS to send notifications (''since 0.10'').""")
87:
88:     smtp_subject_prefix = Option('notification', 'smtp_subject_prefix',
89:                                  '__default__',
90:         """Text to prepend to subject line of notification emails.
91:
92:         If the setting is not defined, then the [$project_name] prefix.
93:         If no prefix is desired, then specifying an empty option
94:         will disable it.(''since 0.10.1'').""")
  • /usr/lib/python2.4/site-packages/trac/ticket/notification.py 수정
    • 라인 30 notification, always_notify_owner 수정
    • 라인 34 notification, always_notify_reporter 수정
28: class TicketNotificationSystem(Component):
29:
30:     always_notify_owner = BoolOption('notification', 'always_notify_owner',
31:                                      'true',
32:         """Always send notifications to the ticket owner (''since 0.9'').""")
33:
34:     always_notify_reporter = BoolOption('notification', 'always_notify_reporter',
35:                                         'true',
36:         """Always send notifications to any address in the ''reporter''
37:         field.""")
38:
39:     always_notify_updater = BoolOption('notification', 'always_notify_updater',
40:                                        'true',
41:         """Always send notifications to the person who causes the ticket
42:         property change.""")

유용한 Script 및 Tip 들

Python 컴파일 (.pyc 만들기)

  • python -m compileall .

https://domain/proj/ 를 요청하면 전체 프로젝트 리스트를 보여주기

https://domain/proj/ 를 요청하면 전체 프로젝트 리스트를 보여주기

  • projenv에 생성된 프로젝트들을 리스팅하기 위한 Perl 스크립트인 index.cgi를 /var/www/html/proj 폴더에 생성한다.
#! /usr/bin/perl
 
use strict;
 
my $trac_path = "/home/wwwtrac/projenv";
my $trac_location = "/proj";
 
#Send header
print "Content-Type: text/html\n\n";
 
#Send content
print "<html>\n";
print "<head>\n";
print "<title>Project listing</title>\n";
print "</head>\n\n";
print "<body>\n";
print " <h1>Project listing</h1>\n";
print " <ul id=\"trac\">\n";
 
opendir(ROOT, $trac_path)
	or die "Unable to open root directory ($trac_path)";
 
while (my $name = readdir(ROOT))
{
   if ($name =~ /^[[:alnum:]]+$/)
   {
       print " <li> <a href=\"$trac_location/$name\">" . ucfirst($name) . "</a></li>\n";
   }
}
 
closedir(ROOT);
 
print " </ul>\n";
print "</body>\n";
 
__END__
  • 웹에서 엑세스 할 수 있도록 적당한 권한을 설정한다.
 
[root@ProjectS proj]# chmod 755 index.cgi
[root@ProjectS proj]# chown apache.apache index.cgi
[root@ProjectS proj]# chcon -t httpd_sys_script_exec_t index.cgi
[root@ProjectS proj]# ls -lZ
-rwxr-xr-x apache apache user_u:object_r:httpd_sys_script_exec_t index.cgi
[root@ProjectS proj]#

  • index.cgi를 수행되도록 하기 위해서는 추가적인 Apache configuration 점검이 필요하다.
  • /etc/httpd/conf/httpd.conf의 다음 옵션이 활성화 되어 있는지 확인하자
AddHandler cgi-script .cgi
  • /etc/httpd/conf.d/ssl.conf에 다음의 <Directory>설정을 추가한다
# =============> for Trac start
 
# ----> set rewrite rule for project resources
RewriteEngine on
RewriteRule ^/proj/+$                   /home/wwwtrac/proj/index.cgi [L]
RewriteCond /home/wwwtrac/projenv/$1    -d
RewriteRule ^/proj/([[:alnum:]]+)(/?.*) /var/www/cgi-bin/trac.cgi$2 [S=1,E=TRAC_ENV:/home/wwwtrac/projenv/$1]
RewriteRule ^/proj/(.*)                 /home/wwwtrac/proj/index.cgi
 
# ----> for running /home/wwwtrac/proj/index.cgi
Alias /proj/ "/home/wwwtrac/proj/"
<Directory "/home/wwwtrac/proj">
    SSLOptions +StdEnvVars
    Options Indexes FollowSymLinks ExecCGI
    PASSEnv LD_LIBRARY_PATH
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>
# <============= end
#
#
  • 설정이 완료되었다면 http서버인 아파치를 다시 실행한다.
 
[root@Hostname /etc/httpd/conf]# service httpd restart

  • 다음과 같이 결과를 확인해 보자.

Trac + SVN 프로젝트 일괄 생성 스트립트 (CentOS 5)

All Version

createProject.sh, Trac+SVN 프로젝트 일괄 생성

#!/bin/sh
 
case "$1" in
"")     echo "please input repository name for create! ex) ./createProject.sh project_name";;
* )     ./scripts/createSVNProj $1
        ./scripts/createTracProj $1
esac
 
#

createSVNProj, Subversion Repository 생성 스크립트

  • /home/svn/publicsvn이 Subversion의 Repository Root 디렉토리이다.
  • SELinux 사용시 /home/svn과 publicsvn은 httpd_sys_script_rw_t 속성을 가져야 한다.
 
[root@ProjectS home]# mkdir svn
[root@ProjectS home]# chcon -t httpd_sys_script_rw_t svn
[root@ProjectS home]# cd svn
[root@ProjectS svn]# mkdir publicsvn
[root@ProjectS svn]# chcon -t httpd_sys_script_rw_t publicsvn

#!/bin/sh
 
flag=0;
 
case "$1" in
"")     echo "please input directory name for create! ex) -directory_name";;
* )     cd /home/svn/publicsvn
        svnadmin create --fs-type fsfs $1
        chmod -R g+w $1
        # SELinux가 활성화 되어 있을 경우
        chcon -R -h -t httpd_sys_script_rw_t $1
        svn mkdir file:///home/svn/publicsvn/$1/trunk -m "trunk directory"
        svn mkdir file:///home/svn/publicsvn/$1/tags -m "tags directory"
        svn mkdir file:///home/svn/publicsvn/$1/branches -m "branches directory"
        svn mkdir file:///home/svn/publicsvn/$1/trunk/src -m "trunk/src director
y"
        flag=1;
        cd /home/svn
esac
 
case "$flag" in
"1")    echo "create success!";
        svn list -R file:///home/svn/publicsvn/$1;;
"0")
        echo "create failed : You read SVN intall-document";;
esac
 
#

V 0.8.4

createTracProj, Trac Project 생성 스크립트

#! /bin/sh
 
echo "-----------------------------------------------"
echo "NOTICE : this script will create a trac project"
echo "-----------------------------------------------"
prjname=$1
cd /home/wwwtrac/projenv
trac-admin $prjname initenv <<EOF
$prjname
/home/svn/publicsvn/$prjname
 
EOF
chown -R apache:apache $prjname
# SELinux 활성화 되어있을 경우
chcon -R -h -t httpd_sys_script_rw_t $prjname
chmod -R g+rwx $prjname
chmod -R o-rwx $prjname
#

V 0.10.4

createTracProj, Trac Project 생성 스크립트

#!/bin/sh
 
echo "-----------------------------------------------"
echo "NOTICE : this script will create a trac project"
echo "-----------------------------------------------"
prjname=$1
cd /home/wwwtrac/projenv
# ----> start usage trac-admin
# trac-admin </path/to/projenv> initenv <projectname> <db> <repostype> <repospath> <templatepath>
# <---- end
trac-admin $prjname initenv <<EOF
$prjname
 
 
/home/svn/publicsvn/$prjname
 
 
EOF
chown -R apache:apache $prjname
# SELinux 활성화 되어있을 경우
chcon -R -h -t httpd_sys_script_rw_t $prjname
chmod -R g+rwx $prjname
chmod -R o-rwx $prjname
#

SSH 채널을 통한 Repository 관리 (Subversion)

Trac Login (Trac Permission 설정 Script 및 인증 관련 설정)

v 0.10.4 logging log_type==file의 Failed to create environment. [Errno 2] 해결

최신 수정 내용 - Plug-In 설치 후 문제 해결

  • Project 생성 최초 초기화 시에만 임시로 syslog를 사용하여 logging 하도록 소스 수정
--- env.py      2007-04-20 22:41:51.000000000 +0900
+++ /usr/lib/python2.4/site-packages/trac/env.py        2009-12-29 16:56:00.000000000 +0900
@@ -14,7 +14,7 @@
 #
 # Author: Jonas Borgström <jonas@edgewall.com>

-import os
+import os, errno

 from trac import db_default, util
 from trac.config import *
@@ -73,22 +73,22 @@
     project_name = Option('project', 'name', 'My Project',
         """Name of the project.""")

-    project_description = Option('project', 'descr', 'My example project',
+    project_description = Option('project', 'descr', 'codeforum project',
         """Short description of the project.""")

-    project_url = Option('project', 'url', 'http://example.org/',
+    project_url = Option('project', 'url', '/proj/',
         """URL of the main project web site.""")

     project_footer = Option('project', 'footer',
-                            'Visit the Trac open source project at<br />'
-                            '<a href="http://trac.edgewall.org/">'
-                            'http://trac.edgewall.org/</a>',
+                            'Visit the Codeforum Projectes at<br />'
+                            '<a href="/proj/">'
+                            '/proj/</a>',
         """Page footer text (right-aligned).""")

     project_icon = Option('project', 'icon', 'common/trac.ico',
         """URL of the icon of the project.""")

-    log_type = Option('logging', 'log_type', 'none',
+    log_type = Option('logging', 'log_type', 'file',
         """Logging facility to use.

         Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""")
@@ -96,7 +96,7 @@
     log_file = Option('logging', 'log_file', 'trac.log',
         """If `log_type` is `file`, this should be a path to the log-file.""")

-    log_level = Option('logging', 'log_level', 'DEBUG',
+    log_level = Option('logging', 'log_level', 'INFO',
         """Level of verbosity in log.

         Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")
@@ -136,7 +136,7 @@

         self.path = path
         self.setup_config(load_defaults=create)
-        self.setup_log()
+        self.setup_log(create)

         from trac.loader import load_components
         load_components(self)
@@ -146,6 +146,7 @@
         else:
             self.verify()

+
         if create:
             for setup_participant in self.setup_participants:
                 setup_participant.environment_created()
@@ -242,6 +243,7 @@
         os.mkdir(os.path.join(self.path, 'conf'))
         _create_file(os.path.join(self.path, 'conf', 'trac.ini'))
         self.setup_config(load_defaults=True)
+       self.setup_log()
         for section, name, value in options:
             self.config.set(section, name, value)
         self.config.save()
@@ -280,12 +282,16 @@
         """Return absolute path to the log directory."""
         return os.path.join(self.path, 'log')

-    def setup_log(self):
+    def setup_log(self,create=False):
         """Initialize the logging sub-system."""
         from trac.log import logger_factory
-        logtype = self.log_type
+        if create:
+           logtype = 'syslog'
+        else:
+            logtype = self.log_type
         logfile = self.log_file
-        if logtype == 'file' and not os.path.isabs(logfile):
+
+       if logtype == 'file' and not os.path.isabs(logfile):
             logfile = os.path.join(self.get_log_dir(), logfile)
         format = self.log_format
         if format:

Plug-In 설치시 프로젝트 생성 및 백업에 문제 발생 - 따라서, 참고만 할것

  • trac-admin의 initenv 명령시 trac.ini의 logging log_type이 file로 생성되도록 env.py를 수정하였을 경우 다음과 같은 에러가 발생한다.
Creating and Initializing Project
Failed to create environment. [Errno 2] No such file or directory: u'/home/wwwtrac/projenv/test4/log/trac.log'
Traceback (most recent call last):
  File "/usr/lib/python2.4/site-packages/trac/scripts/admin.py", line 613, in do_initenv
    options=options)
  File "/usr/lib/python2.4/site-packages/trac/env.py", line 139, in __init__
    self.setup_log()
  File "/usr/lib/python2.4/site-packages/trac/env.py", line 297, in setup_log
    format=format)
  File "/usr/lib/python2.4/site-packages/trac/log.py", line 27, in logger_factory
    hdlr = logging.FileHandler(logfile)
  File "/usr/lib/python2.4/logging/__init__.py", line 757, in __init__
    stream = open(filename, mode)
IOError: [Errno 2] No such file or directory: u'/home/wwwtrac/projenv/test4/log/trac.log'
Failed to initialize environment. 1
Traceback (most recent call last):
  File "/usr/lib/python2.4/site-packages/trac/scripts/admin.py", line 617, in do_initenv
    sys.exit(1)
SystemExit: 1
  • /usr/lib/python-2.4/site-packages/trac/env.py를 아래와 같이 수정하면 잘 동작한다.
--- env_org.py  2008-01-01 04:19:24.000000000 +0900
+++ env.py      2008-01-01 03:58:56.000000000 +0900
@@ -136,7 +136,7 @@

         self.path = path
         self.setup_config(load_defaults=create)
-        self.setup_log()
+        '''self.setup_log()'''

         from trac.loader import load_components
         load_components(self)
@@ -144,6 +144,7 @@
         if create:
             self.create(options)
         else:
+           self.setup_log()
             self.verify()

         if create:
@@ -242,6 +243,7 @@
         os.mkdir(os.path.join(self.path, 'conf'))
         _create_file(os.path.join(self.path, 'conf', 'trac.ini'))
         self.setup_config(load_defaults=True)
+       self.setup_log()
         for section, name, value in options:
             self.config.set(section, name, value)
         self.config.save()

v 0.10.4 trac.ini 의 [project] - footer, url 값이 지정해준 URL prefix를 갖도록 수정(rewrite 기능 사용시)

  • /usr/lib/python2.4/site-packages/trac/scripts/admin.py 수정
--- admin.py.org        2009-12-29 00:45:43.000000000 +0900
+++ admin.py    2009-12-29 00:32:05.000000000 +0900
@@ -607,6 +607,8 @@
                 ('trac', 'repository_dir', repository_dir),
                 ('trac', 'templates_dir', templates_dir),
                 ('project', 'name', project_name),
+               ('project', 'url', 'http://www.codeforum.net/proj/' + project_name + '/'),
+               ('project', 'footer', 'Visit the Codeforum Project at<br /><a href="' + 'http://www.codeforum.net/proj/' + project_name + '/">' + project_name + '</a>'),
             ]
             try:
                 self.__env = Environment(self.envname, create=True,

v 0.10.4 Ticket Notification 의 HTML e-mail 발송

  • /usr/lib/python2.4/site-packages/trac/ticket/notification.py 수정
# -*- coding: utf-8 -*-
#
# Copyright (C) 2003-2006 Edgewall Software
# Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com>
# Copyright (C) 2005-2006 Emmanuel Blot <emmanuel.blot@free.fr>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Daniel Lundin <daniel@edgewall.com>
#
 
import md5
import re
 
from trac import __version__
from trac.core import *
from trac.config import *
from trac.util.text import CRLF, wrap
from trac.notification import NotifyEmail
from trac.wiki import wiki_to_html
 
def ireplace(self,old,new,count=0):
        ''' Behaves like string.replace(), but does so in a case-insensitive
        fashion. '''
        pattern = re.compile(re.escape(old),re.I)
        return re.sub(pattern,new,self,count)
 
class TicketNotificationSystem(Component):
 
    always_notify_owner = BoolOption('notification', 'always_notify_owner',
                                     'true',
        """Always send notifications to the ticket owner (''since 0.9'').""")
 
    always_notify_reporter = BoolOption('notification', 'always_notify_reporter',
                                        'true',
        """Always send notifications to any address in the ''reporter''
        field.""")
 
    always_notify_updater = BoolOption('notification', 'always_notify_updater',
                                       'true',
        """Always send notifications to the person who causes the ticket
        property change.""")
 
 
class TicketNotifyEmail(NotifyEmail):
    """Notification of ticket changes."""
 
    template_name = "ticket_notify_email.cs"
    ticket = None
    newticket = None
    modtime = 0
    from_email = 'trac+ticket@localhost'
    COLS = 75
 
    def __init__(self, env):
        NotifyEmail.__init__(self, env)
        self.prev_cc = []
 
    def notify(self, ticket, newticket=True, modtime=0):
	from trac.test import EnvironmentStub, Mock
	from trac.web.href import Href
	from trac.wiki.formatter import wiki_to_html
 
	req = Mock(href=Href('/'), abs_href=Href('http://www.codeforum.net/proj/'),
		authname='anonymous', args={})
 
        self.ticket = ticket
        self.modtime = modtime
        self.newticket = newticket
        self.reporter = ''
        self.owner = ''
	BRCRLF = '<br />' + CRLF
        self.hdf.set_unescaped('email.ticket_props', self.format_html_props())
        self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr())
        self.hdf['ticket.new'] = self.newticket
        subject = self.format_subj()
        link = self.env.abs_href.ticket(self.ticket.id)
 
	if not self.newticket:
            subject = 'Re: ' + subject
        self.hdf.set_unescaped('email.subject', subject)
        changes = ''
        if not self.newticket and modtime:  # Ticket change
            from trac.ticket.web_ui import TicketModule
            for change in TicketModule(self.env).grouped_changelog_entries(
                ticket, self.db, when=modtime):
                if not change['permanent']: # attachment with same time...
                    continue
                self.hdf.set_unescaped('ticket.change.author',
                                       change['author'])
                self.hdf.set_unescaped('ticket.change.comment',
                                        ireplace(wiki_to_html(change['comment'], self.env, req), "href=\"/", \
				"href=\"" + self.config.get('project', 'url')))
                link += '#comment:%s' % str(change.get('cnum', ''))
                for field, values in change['fields'].iteritems():
                    old = ireplace(wiki_to_html(values['old'], self.env, req), "href=\"/", \
				"href=\"" + self.config.get('project', 'url'))
                    new = ireplace(wiki_to_html(values['new'], self.env, req), "href=\"/", \
				"href=\"" + self.config.get('project', 'url'))
                    pfx = 'ticket.change.%s' % field
                    newv = ''
                    if field == 'description':
                        new_descr = wrap(new, self.COLS, ' ', ' ', CRLF)
                        old_descr = wrap(old, self.COLS, ' ', ' ', CRLF)
                        cdescr = CRLF
                        cdescr += 'Old description:' + 2*BRCRLF + old_descr + CRLF + 2*BRCRLF
                        cdescr += 'New description:' + 2*BRCRLF + new_descr + CRLF + BRCRLF
                        self.hdf.set_unescaped('email.changes_descr', cdescr)
                    elif field == 'cc':
                        (addcc, delcc) = self.diff_cc(old, new)
                        chgcc = ''
                        if delcc:
                            chgcc += '<li>' + wrap(" cc: %s (removed)" % ', '.join(delcc),
                                          self.COLS, ' ', ' ', BRCRLF) + '</li>'
                            chgcc += CRLF
                        if addcc:
                            chgcc += '<li>' + wrap(" cc: %s (added)" % ', '.join(addcc),
                                          self.COLS, ' ', ' ', BRCRLF) + '<li>'
                            chgcc += CRLF
                        if chgcc:
                            changes += chgcc
                        self.prev_cc += old and self.parse_cc(old) or []
                    else:
                        newv = new
                        l = 7 + len(field)
			chg = wrap('%s &rarr; %s' % (values['old'], values['new']), self.COLS - l, '',
                                   l * ' ', CRLF)
                        changes += '<li>' + '%s:  %s%s' % (field, chg, CRLF) + '</li>'
                    if newv:
                        self.hdf.set_unescaped('%s.oldvalue' % pfx, old)
                        self.hdf.set_unescaped('%s.newvalue' % pfx, newv)
            if changes:
                self.hdf.set_unescaped('email.changes_body', '<ul>' + changes + '</ul>')
	if link[0] == '/':
        	self.ticket['link'] = self.config.get('project', 'url') + link
	else:
        	self.ticket['link'] = link
	self.ticket['description'] = ireplace(wiki_to_html(wrap(
		self.ticket.values.get('description', ''), self.COLS,
		initial_indent=' ', subsequent_indent=' ', linesep=CRLF), self.env, req),
		"href=\"/", "href=\"" + self.config.get('project', 'url'))
        self.hdf.set_unescaped('ticket', self.ticket.values)
        NotifyEmail.notify(self, ticket.id, subject)
 
    def format_html_props(self):
        tkt = self.ticket
        BRCRLF = '<br />' + CRLF
        fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')]
        width = [0, 0, 0, 0]
        i = 0
        for f in [f['name'] for f in fields if f['type'] != 'textarea']:
            if not tkt.values.has_key(f):
                continue
            fval = tkt[f]
            if fval.find('\n') != -1:
                continue
            idx = 2 * (i % 2)
            if len(f) > width[idx]:
                width[idx] = len(f)
            if len(fval) > width[idx + 1]:
                width[idx + 1] = len(fval)
            i += 1
        format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
                  ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
        format = ('<tr><th align="right">%s:&nbsp;</th><td style="padding-right: 20px">%s</td>','<th align="right">%s:&nbsp;</th><td>%s</td></tr>'+CRLF)
        l = (width[0] + width[1] + 5)
        sep = l * '-' + '+' + (self.COLS - l) * '-'
        sep = CRLF
        txt = sep + CRLF
        txt = '<table border="0" cellpadding="2" cellspacing="0">' + CRLF
        big = []
        i = 0
        for f in [f for f in fields if f['name'] != 'description']:
            fname = f['name']
            if not tkt.values.has_key(fname):
                continue
            fval = tkt[fname]
            if f['type'] == 'textarea' or '\n' in unicode(fval):
                big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
            else:
                txt += format[i % 2] % (fname.capitalize(), fval)
                i += 1
        if i % 2:
            txt += CRLF
        if big:
            txt += '<td colspan="2">&nbsp;</td></tr>' + CRLF
            txt += sep
            for name, value in big:
                txt += CRLF.join(['<tr align="left"><td colspan="2"><em>' + name + ':' + '</em></td></tr>', '<tr align="left"><td colspan="2">' + value + '</td></tr>'])
                """txt += CRLF.join(['', name + ':', value, '', ''])"""
        txt += sep
        txt += '</table>' + CRLF
        return txt
 
    def format_props(self):
        tkt = self.ticket
        fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')]
        width = [0, 0, 0, 0]
        i = 0
        for f in [f['name'] for f in fields if f['type'] != 'textarea']:
            if not tkt.values.has_key(f):
                continue
            fval = tkt[f]
            if fval.find('\n') != -1:
                continue
            idx = 2 * (i % 2)
            if len(f) > width[idx]:
                width[idx] = len(f)
            if len(fval) > width[idx + 1]:
                width[idx + 1] = len(fval)
            i += 1
        format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
                  ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
        l = (width[0] + width[1] + 5)
        sep = '' # l * '-' + '+' + (self.COLS - l) * '-'
        txt = sep + CRLF
        big = []
        i = 0
        for f in [f for f in fields if f['name'] != 'description']:
            fname = f['name']
            if not tkt.values.has_key(fname):
                continue
            fval = tkt[fname]
            if f['type'] == 'textarea' or '\n' in unicode(fval):
                big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
            else:
                txt += format[i % 2] % (fname.capitalize(), fval)
                i += 1
        if i % 2:
            txt += CRLF
        if big:
            txt += sep
            for name, value in big:
                txt += CRLF.join(['', name + ':', value, '', ''])
        txt += sep
        return txt
 
    def parse_cc(self, txt):
        return filter(lambda x: '@' in x, txt.replace(',', ' ').split())
 
    def diff_cc(self, old, new):
        oldcc = NotifyEmail.addrsep_re.split(old)
        newcc = NotifyEmail.addrsep_re.split(new)
        added = [x for x in newcc if x and x not in oldcc]
        removed = [x for x in oldcc if x and x not in newcc]
        return (added, removed)
 
    def format_hdr(self):
        return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'],
                                                 self.COLS, linesep=CRLF))
 
    def format_subj(self):
        prefix = self.config.get('notification', 'smtp_subject_prefix')
        if prefix == '__default__':
            prefix = '[%s]' % self.config.get('project', 'name')
        if prefix:
            return '%s #%s: %s' % (prefix, self.ticket.id,
                                  self.ticket['summary'])
        else:
            return '#%s: %s' % (self.ticket.id, self.ticket['summary'])
 
    def get_recipients(self, tktid):
        notify_reporter = self.config.getbool('notification',
                                              'always_notify_reporter')
        notify_owner = self.config.getbool('notification',
                                           'always_notify_owner')
        notify_updater = self.config.getbool('notification',
                                             'always_notify_updater')
 
        ccrecipients = self.prev_cc
        torecipients = []
        cursor = self.db.cursor()
 
        # Harvest email addresses from the cc, reporter, and owner fields
        cursor.execute("SELECT cc,reporter,owner FROM ticket WHERE id=%s",
                       (tktid,))
        row = cursor.fetchone()
        if row:
            ccrecipients += row[0] and row[0].replace(',', ' ').split() or []
            self.reporter = row[1]
            self.owner = row[2]
            if notify_reporter:
                torecipients.append(row[1])
            if notify_owner:
                torecipients.append(row[2])
 
        # Harvest email addresses from the author field of ticket_change(s)
        if notify_updater:
            cursor.execute("SELECT DISTINCT author,ticket FROM ticket_change "
                           "WHERE ticket=%s", (tktid,))
            for author,ticket in cursor:
                torecipients.append(author)
 
        # Suppress the updater from the recipients
        updater = None
        cursor.execute("SELECT author FROM ticket_change WHERE ticket=%s "
                       "ORDER BY time DESC LIMIT 1", (tktid,))
        for updater, in cursor:
            break
        else:
            cursor.execute("SELECT reporter FROM ticket WHERE id=%s",
                           (tktid,))
            for updater, in cursor:
                break
 
        if not notify_updater:
            filter_out = True
            if notify_reporter and (updater == self.reporter):
                filter_out = False
            if notify_owner and (updater == self.owner):
                filter_out = False
            if filter_out:
                torecipients = [r for r in torecipients if r and r != updater]
        elif updater:
            torecipients.append(updater)
 
        return (torecipients, ccrecipients)
 
    def get_message_id(self, rcpt, modtime=0):
        """Generate a predictable, but sufficiently unique message ID."""
        s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'),
                               int(self.ticket.id), modtime,
                               rcpt.encode('ascii', 'ignore'))
        dig = md5.new(s).hexdigest()
        host = self.from_email[self.from_email.find('@') + 1:]
        msgid = '<%03d.%s@%s>' % (len(s), dig, host)
        return msgid
 
    def send(self, torcpts, ccrcpts):
        dest = self.reporter or 'anonymous'
        hdrs = {}
        hdrs['Message-ID'] = self.get_message_id(dest, self.modtime)
        hdrs['X-Trac-Ticket-ID'] = str(self.ticket.id)
        hdrs['X-Trac-Ticket-URL'] = self.ticket['link']
	hdrs['Content-Type'] = 'text/html; charset=utf-8'
        if not self.newticket:
            msgid = self.get_message_id(dest)
            hdrs['In-Reply-To'] = msgid
            hdrs['References'] = msgid
        NotifyEmail.send(self, torcpts, ccrcpts, hdrs)
  • /usr/share/trac/templates/ticket_notify_email.cs 다음과 같이 변경
<div style="margin:0;padding:0;font-family:Tahoma,Verdana,Helvetica,Arial,sans-serif,sans;">
<a href="<?cs var:project.url ?>" style="text-decoration:none;display:block;letter-spacing:1px;font-weight:bold;background-color:#f8f8f8;color:#06c;padding:4px;margin:0;border-bottom:1px solid #eee;"><?cs var:project.name ?></a>
<a href="<?cs var:ticket.link ?>" style="text-decoration:none;display:block;background-color:#fbfbfb;color:#555;padding:4px;margin:0;border-bottom:1px solid #ddd;font-size:110%;"><?cs var:email.ticket_body_hdr ?></a>
<?cs if:ticket.new ?>
<div style="padding:1.5em;"><?cs var:ticket.description ?></div>
<?cs else ?>
        <?cs if:ticket.change.comment ?>
<fieldset style="margin:1em 0.25em;border-color:#960;border-left:none;border-right:none;">
<legend style="color:#960;"><small><em>Comment</em><?cs if:!email.changes_body ?> (by <strong><?cs var:ticket.change.author ?></strong>)<?cs /if ?></small></legend>
<div style="padding:1.5em;"><?cs var:ticket.change.comment ?></div>
</fieldset>
        <?cs /if ?>
        <?cs if:email.changes_body ?>
<fieldset style="margin:1em 0.25em;border-color:#069;border-left:none;border-right:none;">
<legend style="color:#069;"><small><em>Changes</em> (by <strong><?cs var:ticket.change.author ?></strong>)</small></legend>
<?cs var:email.changes_body ?>
</fieldset>
        <?cs /if ?>
        <?cs if:email.changes_descr ?>
<div style="padding:1.5em;"><?cs var:email.changes_descr ?></div>
        <?cs /if ?>
<?cs /if ?>
<div style="font-size:80%;color:#999;border-top:1px dotted #999;"><?cs var:email.ticket_props ?></div>
<cite style="display:block;padding:4px;background-color:#f0f0f0;color:#999;font-size:95%;border-top:1px dotted #ccc;"><?cs var:project.descr ?></cite>
</div>

logging log_type==syslogd 로 했을때 발생하는 permission denied 문제

Notification(Ticket 변경의 e-mail을 통한 알림) 기능 사용

  • 각 Project 홈 conf/trac.ini 의 [notification] 섹션 사용
  • Project 홈 생성시 기본적인 정보의 trac.ini 생성
    • /usr/lib/python2.4/site-packages/trac/notification.py 수정

notification을 true로 했음에도 동작하지 않는 문제

  • 이 정도쯤 허용하고 나면 httpd 데몬에 대한 SELinux의 정책 검사를 비활성화 시키는 것과 어떤 차이가 있을까 하는 생각이 든다.
  • SELinux를 사용하는 것이 무색할 정도로 httpd에 대한 허용을 해버린 걸까?
  • 아니야 그래도 기존 Linux 보안 정책을 쓰는것 보다는 SELinux를 활성화 시켜두는게 나을 거야! ㅋㅋ
 
[root@ProjectS policy]# egrep "netif|trac.cgi" /var/log/audit/audit.log | audit2allow
# ============= httpd_sys_script_t ==============
allow httpd_sys_script_t devlog_t:sock_file write;
allow httpd_sys_script_t file_t:dir { search getattr };
allow httpd_sys_script_t file_t:file { read getattr };
allow httpd_sys_script_t self:netlink_route_socket { write getattr read bind create nlmsg_read };
allow httpd_sys_script_t self:tcp_socket { read write create connect };
allow httpd_sys_script_t self:unix_dgram_socket { write create connect };
allow httpd_sys_script_t smtp_port_t:tcp_socket name_connect;
allow httpd_sys_script_t syslogd_t:unix_dgram_socket sendto;
allow httpd_sys_script_t unlabeled_t:packet { recv send };
allow httpd_sys_script_t user_home_t:dir { search getattr };
allow httpd_sys_script_t user_home_t:file { read getattr };
# ============= unconfined_t ==============
allow unconfined_t httpd_sys_script_t:file relabelto;
[root@ProjectS policy]# egrep "netif|trac.cgi" /var/log/audit/audit.log | audit2allow -M tracsocket
******************** IMPORTANT ***********************
To make this policy package active, execute:
semodule -i tracsocket.pp
[root@ProjectS policy]# more tracsocket.te
module tracsocket 1.0;
require {
type unconfined_t;
type unlabeled_t;
type user_home_t;
type smtp_port_t;
type syslogd_t;
type file_t;
type devlog_t;
type httpd_sys_script_t;
class packet { recv send };
class tcp_socket { read write name_connect create connect };
class file { read relabelto getattr };
class sock_file write;
class netlink_route_socket { write getattr read bind create nlmsg_read };
class unix_dgram_socket { write create connect sendto };
class dir { search getattr };
}
# ============= httpd_sys_script_t ==============
allow httpd_sys_script_t devlog_t:sock_file write;
allow httpd_sys_script_t file_t:dir { search getattr };
allow httpd_sys_script_t file_t:file { read getattr };
allow httpd_sys_script_t self:netlink_route_socket { write getattr read bind create nlms
g_read };
allow httpd_sys_script_t self:tcp_socket { read write create connect };
allow httpd_sys_script_t self:unix_dgram_socket { write create connect };
allow httpd_sys_script_t smtp_port_t:tcp_socket name_connect;
allow httpd_sys_script_t syslogd_t:unix_dgram_socket sendto;
allow httpd_sys_script_t unlabeled_t:packet { recv send };
allow httpd_sys_script_t user_home_t:dir { search getattr };
allow httpd_sys_script_t user_home_t:file { read getattr };
# ============= unconfined_t ==============
allow unconfined_t httpd_sys_script_t:file relabelto;
[root@ProjectS policy]# semodule -i tracsocket.pp

  • 위의 Customized Policy를 적용하고서 표면적으로 보이는 audit error는 없었다. 그러나 Ticket 등록시 위 등록 메일 계정으로 어떤 메일이 온다거나 하는 것이 없었다.
  • 분석 결과 아래 configuration의 계정은 전적으로 메일 전달을 위한 계정으로 쓰이는 듯 하다. 모든 Notificaiton이 이 계정으로 보내진다거나 하는 것은 아닌것 같다.
  • owner, reporter 관계에 따라 위 계정을 사용해 메일 전달을 위해 잠깐 사용하는 것으로 보인다.
  • 따라서 계속 Notification의 동작이 없는 것처럼 보일때, notification에 대한 configuration 점검 및 STMP 서버쪽의 로그를 점검해 볼 필요가 있다.
  • 메일 서버 점검을 통해 접속 시도가 있었다는 것을 알 수 있었다. 다만 접속만 했다는것. 따라서 이제 Trac에는 문제가 없다.
  • always_notify_owner, always_notify_reporter를 true로 설정한후 등록한 메일 주소들로 메일이 전달되는 것을 확인할 수 있었다.
  • TODO : Notification의 쓰임새를 익히도록 하자
[notification]
always_notify_owner = true
always_notify_reporter = true
always_notify_updater = true
mime_encoding = base64
smtp_always_bcc =
smtp_always_cc =
smtp_default_domain =
smtp_enabled = true
smtp_from = user@codeforum.net
smtp_password = password
smtp_port = 25
smtp_replyto = user@codeforum.net
smtp_server = mail.codeforum.net # 또는 내부망인 경우 ex : 192.168.1.11
smtp_subject_prefix = __default__
smtp_user = user
use_public_cc = false
use_short_addr = false
use_tls = false
Jan  1 07:44:50 codeforum sendmail[74905]: AUTH=server, relay=codeforum.net [218
.158.45.145], authid=user, mech=PLAIN, bits=0
Jan  1 07:44:50 codeforum sendmail[74905]: lBVMiovk074905: codeforum.net [218.15
8.45.145] did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA
  • did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA 접속만 하고 아무것도 하지 않았다는 뜻 같다. 엄밀히 문제가 있는 것은 아닌듯.. 상단의 notification configuration에서 always_notify_owner, always_notify_reporter를 true로 설정한 후 메일 전달되는 것 확인
  • Notification은 실제 사용하면서 지속적인 튜닝이 필요한 듯 하다.
  • SELINUX가 활성화된 상태에서 smtp_server로 IP가 아닌 도메인 네임을 사용하는 경우 getaddrinfo 함수에서 다음과 같은 문제가 발생할 수 있다.

각 project/log/trac.log 확인


2009-03-30 17:00:16,447 Trac[web_ui] ERROR: Failure sending notification on change to ticket #3: (-3, 'Temporary failure in name resolution')
Traceback (most recent call last):
  File "/usr/lib/python2.4/site-packages/trac/ticket/web_ui.py", line 562, in _do_save
    tn.notify(ticket, newticket=False, modtime=now)
  File "/usr/lib/python2.4/site-packages/trac/ticket/notification.py", line 129, in notify
    NotifyEmail.notify(self, ticket.id, subject)
  File "/usr/lib/python2.4/site-packages/trac/notification.py", line 216, in notify
    Notify.notify(self, resid)
  File "/usr/lib/python2.4/site-packages/trac/notification.py", line 114, in notify
    self.begin_send()
  File "/usr/lib/python2.4/site-packages/trac/notification.py", line 278, in begin_send
    self.server = smtplib.SMTP(self.smtp_server, self.smtp_port)
  File "/usr/lib/python2.4/smtplib.py", line 244, in __init__
    (code, msg) = self.connect(host, port)
  File "/usr/lib/python2.4/smtplib.py", line 292, in connect
    for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
gaierror: (-3, 'Temporary failure in name resolution')

  • phython의 UDP Socket 사용과 관련된 권한 정책의 문제임으로 다음과 같이 해결한다
 
[root@ProjectS policy]# egrep "netif|trac.fcgi" /var/log/audit/audit.log | audit2allow
# ============= httpd_sys_script_t ==============
allow httpd_sys_script_t self:udp_socket { write read create connect getattr };
[root@ProjectS policy]# egrep "netif|trac.fcgi" /var/log/audit/audit.log | audit2allow -M tracsocket4fcgi
******************** IMPORTANT ***********************
To make this policy package active, execute:
semodule -i tracsocket4fcgi.pp
[root@ProjectS policy]# semodule -i tracsocket4fcgi.pp

mod_fastcgi 지원

  • 먼저 httpd의 update가 필요하면 해준다
 
[root@ProjectS mod_fastcgi-2.4.6]$ yum update httpd
Loading "installonlyn" plugin
Loading "fastestmirror" plugin
Loading "priorities" plugin
Setting up Update Process
Setting up repositories
Loading mirror speeds from cached hostfile
Reading repository metadata in from local files
0 packages excluded due to repository priority protections
Resolving Dependencies
--> Populating transaction set with selected packages. Please wait.
---> Downloading header for httpd to pack into transaction set.
httpd-2.2.3-11.el5_1.cent 100% |=========================| 55 kB 00:00
---> Package httpd.i386 0:2.2.3-11.el5_1.centos.3 set to be updated
--> Running transaction check
--> Processing Dependency: httpd = 0:2.2.3-11.el5.centos for package: mod_ssl
--> Processing Dependency: httpd = 2.2.3-11.el5.centos for package: httpd-manual
--> Restarting Dependency Resolution with new changes.
--> Populating transaction set with selected packages. Please wait.
---> Downloading header for httpd-manual to pack into transaction set.
httpd-manual-2.2.3-11.el5 100% |=========================| 36 kB 00:00
---> Package httpd-manual.i386 0:2.2.3-11.el5_1.centos.3 set to be updated
---> Downloading header for mod_ssl to pack into transaction set.
mod_ssl-2.2.3-11.el5_1.ce 100% |=========================| 12 kB 00:00
---> Package mod_ssl.i386 1:2.2.3-11.el5_1.centos.3 set to be updated
--> Running transaction check
Dependencies Resolved
=============================================================================
Package Arch Version Repository Size
=============================================================================
Updating:
httpd i386 2.2.3-11.el5_1.centos.3 updates 1.1 M
Updating for dependencies:
httpd-manual i386 2.2.3-11.el5_1.centos.3 updates 832 k
mod_ssl i386 1:2.2.3-11.el5_1.centos.3 updates 84 k
Transaction Summary
=============================================================================
Install 0 Package(s)
Update 3 Package(s)
Remove 0 Package(s)
Total download size: 2.0 M
Is this ok [y/N]: y
Downloading Packages:
(1/3): httpd-2.2.3-11.el5 100% |=========================| 1.1 MB 00:02
(2/3): httpd-manual-2.2.3 100% |=========================| 832 kB 00:02
(3/3): mod_ssl-2.2.3-11.e 100% |=========================| 84 kB 00:00
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
Updating : httpd ######################### [1/6]
Updating : httpd-manual ######################### [2/6]
Updating : mod_ssl ######################### [3/6]
Cleanup : httpd ######################### [4/6]
Cleanup : httpd-manual ######################### [5/6]
Cleanup : mod_ssl ######################### [6/6]
Updated: httpd.i386 0:2.2.3-11.el5_1.centos.3
Dependency Updated: httpd-manual.i386 0:2.2.3-11.el5_1.centos.3 mod_ssl.i386 1:2.2.3-11.el5_1.centos.3
Complete!

  • httpd-devel package 설치
 
[root@ProjectS mod_fastcgi-2.4.6]$ yum list all "httpd*"
Loading "installonlyn" plugin
Loading "fastestmirror" plugin
Loading "priorities" plugin
Setting up repositories
Loading mirror speeds from cached hostfile
Reading repository metadata in from local files
0 packages excluded due to repository priority protections
Installed Packages
httpd.i386 2.2.3-11.el5.centos installed
httpd-manual.i386 2.2.3-11.el5.centos installed
Available Packages
httpd.i386 2.2.3-11.el5_1.centos. updates
httpd-devel.i386 2.2.3-11.el5_1.centos. updates
httpd-manual.i386 2.2.3-11.el5_1.centos. updates
[root@ProjectS mod_fastcgi-2.4.6]$ yum install httpd-devel
Loading "installonlyn" plugin
Loading "fastestmirror" plugin
Loading "priorities" plugin
Setting up Install Process
Setting up repositories
Loading mirror speeds from cached hostfile
Reading repository metadata in from local files
0 packages excluded due to repository priority protections
Parsing package install arguments
Resolving Dependencies
--> Populating transaction set with selected packages. Please wait.
---> Downloading header for httpd-devel to pack into transaction set.
httpd-devel-2.2.3-11.el5_ 100% |=========================| 17 kB 00:00
---> Package httpd-devel.i386 0:2.2.3-11.el5_1.centos.3 set to be updated
--> Running transaction check
--> Processing Dependency: apr-devel for package: httpd-devel
--> Processing Dependency: apr-util-devel for package: httpd-devel
--> Restarting Dependency Resolution with new changes.
--> Populating transaction set with selected packages. Please wait.
---> Downloading header for apr-util-devel to pack into transaction set.
apr-util-devel-1.2.7-6.i3 100% |=========================| 9.4 kB 00:00
---> Package apr-util-devel.i386 0:1.2.7-6 set to be updated
---> Downloading header for apr-devel to pack into transaction set.
apr-devel-1.2.7-11.i386.r 100% |=========================| 14 kB 00:00
---> Package apr-devel.i386 0:1.2.7-11 set to be updated
--> Running transaction check
Dependencies Resolved
=============================================================================
Package Arch Version Repository Size
=============================================================================
Installing:
httpd-devel i386 2.2.3-11.el5_1.centos.3 updates 146 k
Installing for dependencies:
apr-devel i386 1.2.7-11 base 237 k
apr-util-devel i386 1.2.7-6 base 54 k
Transaction Summary
=============================================================================
Install 3 Package(s)
Update 0 Package(s)
Remove 0 Package(s)
Total download size: 438 k
Is this ok [y/N]: y
Downloading Packages:
(1/3): apr-util-devel-1.2 100% |=========================| 54 kB 00:00
(2/3): httpd-devel-2.2.3- 100% |=========================| 146 kB 00:00
(3/3): apr-devel-1.2.7-11 100% |=========================| 237 kB 00:00
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
Installing: apr-devel ######################### [1/3]
Installing: apr-util-devel ######################### [2/3]
Installing: httpd-devel ######################### [3/3]
Installed: httpd-devel.i386 0:2.2.3-11.el5_1.centos.3
Dependency Installed: apr-devel.i386 0:1.2.7-11 apr-util-devel.i386 0:1.2.7-6
Complete!

  • mod_fastcgi 소스 설치
 
[root@ProjectS 8_install_trac]# wget http: //www.fastcgi.com/dist/mod_fastcgi-2.4.6.tar.gz
--02:05:44-- http://www.fastcgi.com/dist/mod_fastcgi-2.4.6.tar.gz
Resolving www.fastcgi.com... 209.212.66.200
Connecting to www.fastcgi.com|209.212.66.200|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 100230 (98K) [application/x-tar]
Saving to: `mod_fastcgi-2.4.6.tar.gz'
100%[===============================================================================>] 100,230 115K/s in 0.9s
02:05:46 (115 KB/s) - `mod_fastcgi-2.4.6.tar.gz' saved [100230/100230]
[root@ProjectS 8_install_trac]# ls
mod_fastcgi-2.4.6.tar.gz pysqlite-2.3.5.tar.gz trac-0.10.4.tar.gz trac_0.10.4_kor_rpms_tar.gz trac_rpms_for_CentOS5
[root@ProjectS 8_install_trac]# tar zxvf mod_fastcgi-2.4.6.tar.gz
[root@ProjectS 8_install_trac]# cd mod_fastcgi-2.4.6
[root@ProjectS mod_fastcgi-2.4.6]# cp Makefile.AP2 Makefile
[root@ProjectS mod_fastcgi-2.4.6]# vi Makefile
top_dir = /usr/lib/httpd
[root@ProjectS mod_fastcgi-2.4.6]# make
/usr/lib/apr-1/build/libtool --silent --mode=compile gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2
-fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -I/usr/include/httpd -I. -I/usr/include/apr-1
-I/usr/kerberos/include -prefer-pic -c mod_fastcgi.c && touch mod_fastcgi.slo
mod_fastcgi.c: In function 'open_connection_to_fs':
mod_fastcgi.c:1083: warning: dereferencing type-punned pointer will break strict-aliasing rules
/usr/lib/apr-1/build/libtool --silent --mode=compile gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2
-fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -I/usr/include/httpd -I. -I/usr/include/apr-1
-I/usr/kerberos/include -prefer-pic -c fcgi_pm.c && touch fcgi_pm.slo
fcgi_pm.c: In function 'dynamic_read_msgs':
fcgi_pm.c:1102: warning: dereferencing type-punned pointer will break strict-aliasing rules
/usr/lib/apr-1/build/libtool --silent --mode=compile gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2
-fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -I/usr/include/httpd -I. -I/usr/include/apr-1
-I/usr/kerberos/include -prefer-pic -c fcgi_util.c && touch fcgi_util.slo
/usr/lib/apr-1/build/libtool --silent --mode=compile gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2
-fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -I/usr/include/httpd -I. -I/usr/include/apr-1
-I/usr/kerberos/include -prefer-pic -c fcgi_protocol.c && touch fcgi_protocol.slo
/usr/lib/apr-1/build/libtool --silent --mode=compile gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2
-fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -I/usr/include/httpd -I. -I/usr/include/apr-1
-I/usr/kerberos/include -prefer-pic -c fcgi_buf.c && touch fcgi_buf.slo
/usr/lib/apr-1/build/libtool --silent --mode=compile gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2
-fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -I/usr/include/httpd -I. -I/usr/include/apr-1
-I/usr/kerberos/include -prefer-pic -c fcgi_config.c && touch fcgi_config.slo
fcgi_config.c: In function 'fcgi_config_new_static_server':
fcgi_config.c:769: warning: format '%ld' expects type 'long int', but argument 3 has type 'gid_t'
fcgi_config.c:774: warning: format '%ld' expects type 'long int', but argument 3 has type 'uid_t'
fcgi_config.c:808: warning: dereferencing type-punned pointer will break strict-aliasing rules
fcgi_config.c:833: warning: dereferencing type-punned pointer will break strict-aliasing rules
fcgi_config.c: In function 'fcgi_config_new_external_server':
fcgi_config.c:962: warning: format '%ld' expects type 'long int', but argument 3 has type 'gid_t'
fcgi_config.c:967: warning: format '%ld' expects type 'long int', but argument 3 has type 'uid_t'
fcgi_config.c:999: warning: dereferencing type-punned pointer will break strict-aliasing rules
fcgi_config.c:1016: warning: dereferencing type-punned pointer will break strict-aliasing rules
/usr/lib/apr-1/build/libtool --silent --mode=link gcc -pthread -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions
-fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables
-Wl,-z,relro -o mod_fastcgi.la -rpath /usr/lib/httpd/modules -module -avoid-version mod_fastcgi.lo fcgi_pm.lo
fcgi_util.lo fcgi_protocol.lo fcgi_buf.lo fcgi_config.lo
[root@ProjectS mod_fastcgi-2.4.6]# make install

  • Httpd 설정 (/etc/httpd/conf.d/ssl.conf)
<IfModule mod_fastcgi.c>
   AddHandler fastcgi-script .fcgi
   FastCgiIpcDir /var/www/cgi-bin
</IfModule>
LoadModule fastcgi_module /etc/httpd/modules/mod_fastcgi.so
 
RewriteEngine on
RewriteRule ^/proj/+$                   /home/protectedtrac/proj/index.cgi [L]
RewriteCond /home/protectedtrac/projenv/$1      -d
RewriteRule ^/proj/([[:alnum:]]+)(/?.*) /var/www/cgi-bin/trac.fcgi$2 [S=1,E=TRAC_ENV:/home/protectedtrac/projenv/$1]
RewriteRule ^/proj/(.*)                 /home/protectedtrac/proj/index.cgi

Plug-In

  • # python setup.py bdist_egg
    • 소스를 컴파일하여 하나의 egg 파일로 만드는 과정
    • 따라서, 생성된 XXX.egg 파일에 모든 컴파일된 소스가 들어가게 된다.
    • 그러므로, 소스를 수정하게되면 이 빌드 과정을 다시 수행하고, 다시 인스톨 해야한다.

TracGanttCalendar

TracGanttGalendar

3. 설치
# cd /usr/local/src/ganttcalendarplugin
# python setup.py bdist_egg
easy_install을 이용하여 간단히 설치했습니다.
# easy_install .
 
datefield 플러그인 설치
# easy_install http://trac-hacks.org/svn/datefieldplugin/0.10
 
4. 설정
trac.ini을 아래 처럼 수정해줍니다. **
[components]
ganttcalendar.ticketcalendar.ticketcalendarplugin = enabled
ganttcalendar.ticketgantt.ticketganttchartplugin = enabled
datefield.filter.datefieldmodule = enabled
 
[datefield]
format = ymd
separator = /
 
[ticket-custom]
complete = select
complete.label = % Complete
complete.options = 0|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|100
complete.order = 3
due_assign = text
due_assign.label = Start(YYYY/MM/DD)
due_assign.order = 1
due_assign.date = true
due_assign.date_empty = true
due_close = text
due_close.label = End(YYYY/MM/DD)
due_close.order = 2
due_close.date = true
due_close.date_empty = true
 
그리고 서비스 (apache 또는 trac)를 재시작해 줍니다. 안그러면 적용이 안되기도 하더군요
  • 캘린더, 간트 : TICKET_VIEW 권한을 갖는 사용자에게만 Display
    • ticketcalendar.py
      --- ganttcalendar/ticketcalendar.py     2009-03-11 22:25:38.000000000 +0900
      +++ ../ganttcalendarplugin-0.10-ko_perm_fix/ganttcalendar/ticketcalendar.py     2009-12-30 22:56:34.000000000 +0900
      @@ -17,8 +17,9 @@
               return 'ticketcalendar'
      
           def get_navigation_items(self, req):
      -        yield ('mainnav', 'ticketcalendar',
      -               Markup(u'<a href="%s">캘린더</a>', req.href.ticketcalendar()))
      +       if req.perm.has_permission('TICKET_VIEW'):
      +             yield ('mainnav', 'ticketcalendar',
      +                    Markup(u'<a href="%s">캘린더</a>', req.href.ticketcalendar()))
      
           # IRequestHandler methods
           def match_request(self, req):
    • ticketgantt.py
      --- ../ganttcalendarplugin-0.10-ko/ganttcalendar/ticketgantt.py 2009-03-11 22:27:36.000000000 +0900
      +++ ganttcalendar/ticketgantt.py        2009-12-30 22:57:17.000000000 +0900
      @@ -17,8 +17,9 @@
               return 'ticketgantt'
      
           def get_navigation_items(self, req):
      -        yield ('mainnav', 'ticketgantt',
      -               Markup(u'<a href="%s">간트</a>', req.href.ticketgantt()))
      +       if req.perm.has_permission('TICKET_VIEW'):
      +             yield ('mainnav', 'ticketgantt',
      +                    Markup(u'<a href="%s">간트</a>', req.href.ticketgantt()))
      
           # IRequestHandler methods
           def match_request(self, req):

Plug-In 삭제

  • Uninstalling
    • easy_install or python setup.py does not have an uninstall feature. Hower, it is usually quite trivial to remove a globally installed egg and reference:
      • (유효)Do easy_install -m [plugin name] to remove references from $PYTHONLIB/site-packages/easy-install.pth when the plugin installed by setuptools.
      • Delete executables from /usr/bin, /usr/local/bin or C:\\Python*\Scripts. For search what executables are there, you may refer to [console-script] section of setup.py.
      • (유효)Delete the .egg file or folder from where it is installed, usually inside $PYTHONLIB/site-packages/.
      • Restart web server.
      • If you are uncertain about the location of the egg, here is a small tip to help locate an egg (or any package) - replace myplugin with whatever namespace the plugin uses (as used when enabling the plugin):
        >>> import myplugin
        >>> print myplugin.__file__
        /opt/local/python24/lib/site-packages/myplugin-0.4.2-py2.4.egg/myplugin/__init__.pyc

관련 주제들

Discussion

Enter your comment (wiki syntax is allowed):
If you can't read the letters on the image, download this .wav file to get them read to you.
 
trac.txt · 마지막 수정: 2009/12/31 00:24 작성자 pitoosung
 
이 위키의 내용은 다음의 라이센스에 따릅니다 :CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki