CVS服务器安装配置
时间:2006-01-24 来源:cnscn2008
#apt-get install cvs
2.建立cvs用户组,便于管理cvs用户
groupadd cvs
3.建立cvsroot用户,属于cvs组(组名必须为cvs),根目录为/home/cvsroot,不许登陆
useradd -g cvs -s /sbin/nologin cvsroot
4.改变/home/cvsroot的目录属性
chmod 775 /home/cvsroot
5.初始化cvs源代码库,此操作生成目录/home/cvsroot/CVSROOT,其下为一些初始化文件
cvs -d /home/cvsroot init
6.创建可以登陆cvs服务的用户及密码,需要创建文件passwd
vi /home/cvsroot/CVSROOT/passwd
文件内容如下:
weiqiong:xxxxxx:cvsroot
chenxu:xxxxxx:cvsroot
此文件的意思是weiqiong和chenxu两个用户可以登陆cvs服务器,登陆后其权限为用户cvsroot的权限
注意:cvs用户和服务器用户是可以不一样的
7.xxxxxx为密码,由以下文件生成:
vi /home/cvsroot/passwdgen.pl
文件内容:
#!/usr/bin/perl
srand (time());
my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))";
my $salt = sprintf ("%c%c", eval $randletter, eval $randletter);
my $plaintext = shift;
my $crypttext = crypt ($plaintext, $salt);
print "${crypttext} ";
8.如果需要密码为:123456,则敲入:
passwdgen.pl "123456"
回车即可得到加密密码,用其替换passwd文件中的xxxxxx
9.加入cvs服务(一般的redhat上缺省就有cvs服务,所以不用加)
vi /etc/services
cvspserver 2401/tcp #pserver cvs service
cvspserver 2401/udp #pserver cvs service
10.一般cvs服务由inted来唤起,因此需要改动inetd提供的服务,如果你的redhat使用的是inetd方式,
则在文件/etc/inetd.conf中加入如下的内容:
·在Debian 或 redhat7.3以下
#vi /etc/init.d/inet.conf 加入
# cvspserver stream tcp nowait root /usr/bin/cvs /usr/bin/cvs --allow-root=/home/cvsroot --allow-root=/home/cvsroot2 --allow-root=/home/cvsroot3 pserver
·redhat7.3以上使用的是xinetd方式,所以在xinetd.d目录下添加需要启动的服务:
cd /etc/xinetd.d
vi cvspserver
文件内容[ =前后一定要有空格 ]
service cvspserver
{
disable = no
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/bin/cvs
server_args = -f --allow-root=/home/cvsroot pserver
log_on_failure += USERID
only_from = 192.168.0.0/24 #限制用户访问的IP
}
11.重新启动inetd或者xinetd:
/etc/init.d/inetd restart
或
/etc/rc.d/init.d/xinetd restart
12.检查cvspserver服务是否已经启动
netstat -l |grep cvspserver
应该有如下结果:
tcp 0 0 *:cvspserver *:* LISTEN
======================================
[From]http://www.worldhello.net
2.1.1. 运行 CVS
-
/etc/services
...
cvspserver 2401/tcp
cvspserver 2401/udp
... -
用xinetd运行: /etc/xinetd.d/cvs
service cvspserver
{
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/bin/cvs
server_args = -f --allow-root=/repos/root1 --allow-root=/repos/root2 pserver
disable = no
} -
用inetd运行: /etc/inetd.conf
...
cvspserver stream tcp nowait root /usr/bin/cvs cvs -f --allow-root=/repos/project --allow-root=/repos/user pserver
...
2.1.2. 添加帐号和设置权限
-
创建系统帐号
假设目录 /repos/project 作为多人共享项目的版本控制根目录,需要用组权限控制;/repos/user作为存放个人独占地版本控制根目录。管理员帐号为cvsroot,项目版本控制的公共帐号为 cvsproject,用户版本控制的公共帐号为 cvsuser,相应的用户组为 cvsroot, cvsproject, cvsuser。
$ groupadd cvsproject
$ groupadd cvsuser
$ groupadd cvsroot
$ useradd -g cvsproject -s /sbin/nologin cvsproject
$ useradd -g cvsuser -s /sbin/nologin cvsuser
$ useradd -g cvsroot -G cvsproject,cvsuser cvsroot
$ useradd -g cvsuser -s /sbin/nologin cvs_jiangxin
$ useradd -g cvsuser -s /sbin/nologin cvs_johnson设置 cvsroot 属于多个组,这样 cvsroot 用户除了进行系统维护外(如添加新的工程),还可以和其它组用户一样具有管理代码的权限。
用来和CVS用户帐号一一对应的系统帐号。系统帐号禁止登录,密码设置在相应的 CVS 对应帐号文件中设置。
-
创建CVS根目录
$ mkdir -p /repos/project
$ mkdir -p /repos/user
$ chown cvsroot:cvsroot /repos/project
$ chown cvsroot:cvsroot /repos/user
$ chmod 775 /repos
$ chmod 2775 /repos/project
$ chmod 2775 /repos/user
$ su - cvsroot
$ cvs -d /repos/project init
$ cvs -d /repos/user init运行完毕 cvs init 之后,在CVS根目录下创建了配置目录CVSROOT,权限如下:
$ ls -l /repos/project/CVSROOT/
-r--r--r-- 1 cvsroot cvsroot 493 Jan 21 10:37 checkoutlist
-r--r--r-- 1 cvsroot cvsroot 696 Jan 21 10:37 checkoutlist,v
-r--r--r-- 1 cvsroot cvsroot 760 Jan 21 10:37 commitinfo
-r--r--r-- 1 cvsroot cvsroot 963 Jan 21 10:37 commitinfo,v
-r--r--r-- 1 cvsroot cvsroot 527 Jan 21 10:37 config
-r--r--r-- 1 cvsroot cvsroot 730 Jan 21 10:37 config,v
-r--r--r-- 1 cvsroot cvsroot 753 Jan 21 10:37 cvswrappers
-r--r--r-- 1 cvsroot cvsroot 956 Jan 21 10:37 cvswrappers,v
-r--r--r-- 1 cvsroot cvsroot 1025 Jan 21 10:37 editinfo
-r--r--r-- 1 cvsroot cvsroot 1228 Jan 21 10:37 editinfo,v
drwxrwxr-x 2 cvsroot cvsroot 4096 Jan 21 10:37 Emptydir
-rw-rw-rw- 1 cvsroot cvsroot 0 Jan 21 10:37 history
-r--r--r-- 1 cvsroot cvsroot 1141 Jan 21 10:37 loginfo
-r--r--r-- 1 cvsroot cvsroot 1344 Jan 21 10:37 loginfo,v
-r--r--r-- 1 cvsroot cvsroot 1151 Jan 21 10:37 modules
-r--r--r-- 1 cvsroot cvsroot 1354 Jan 21 10:37 modules,v
-r--r--r-- 1 cvsroot cvsroot 564 Jan 21 10:37 notify
-r--r--r-- 1 cvsroot cvsroot 767 Jan 21 10:37 notify,v
-r--r--r-- 1 cvsroot cvsroot 649 Jan 21 10:37 rcsinfo
-r--r--r-- 1 cvsroot cvsroot 852 Jan 21 10:37 rcsinfo,v
-r--r--r-- 1 cvsroot cvsroot 879 Jan 21 10:37 taginfo
-r--r--r-- 1 cvsroot cvsroot 1082 Jan 21 10:37 taginfo,v
-rw-rw-rw- 1 cvsroot cvsroot 0 Jan 21 10:37 val-tags
-r--r--r-- 1 cvsroot cvsroot 1026 Jan 21 10:37 verifymsg
-r--r--r-- 1 cvsroot cvsroot 1229 Jan 21 10:37 verifymsg,v可以看出文件 history, val-tags 的权限是任何人可读写,其它文件的权限是任何帐号只读。
文件 CVSROOT/val-tags 用来确定是否一个TAG是可用的;文件 CVSROOT/history 用来记录CVS的访问记录。
-
创建 CVS 用户帐号
使用系统帐号不安全,而CVS提供了独立于系统的用户帐号管理。
使用配置文件 CVSROOT/passwd, CVSROOT/passwd, CVSROOT/passwd ,来管理帐号。
$ cat /repos/project/CVSROOT/passwd
jiangxinroot:_passwd_here_:cvsroot
jiangxin:_passwd_here_:cvsproject
johnson:_passwd_here_:cvsproject
anonymous::cvsproject
$ cat /repos/project/CVSROOT/readers
anonymous用户帐号 jiangxinroot,具有和系统帐号 cvsroot 同样权限。
用户帐号 jiangxin, johnson,都具有系统帐号 cvsproject 同样的权限,
匿名帐号 anonymous 的密码为空。
readers 文件中出现的用户帐号,只具有只读权限。
-
CVS 用户修改口令
我写了一个程序,作为用户登录的 shell,允许用户远程 SSH 登录,修改自己的 Unix 系统口令以及 CVS 账号口令,参见:附件。如果您有更好的方法,不吝赐教。
2.1.3. 创建工程
只能以 cvsroot 用户创建工程。因为 cvs 根目录的权限设置为 cvsroot 帐户可写。
-
在客户端创建工程
client$ cvs -d :pserver:[email protected]:/repos/project login
client$ cvs -d :pserver:[email protected]:/repos/project import -m "add module test, vendor jiangxin, init_tag start." test jiangxin start
$ ls -l /repos/project
drwxrwxr-x 3 cvsroot cvsroot 4096 Jan 21 10:54 CVSROOT
drwxrwsr-x 2 cvsroot cvsroot 4096 Jan 21 11:00 test
$ chown -R cvsproject:cvsproject test
$ chmod -R 770 test
$ chmod -R g+s test
client$ cvs -d :pserver:[email protected]:/repos/project co test
client$ cvs -d :pserver:[email protected]:/repos/project co -d test2 test于是创建了多用户共享的工程 test。
-
另一种创建工程的方法:在服务器端创建工程
可以直接在服务器端创建目录,设置权限,即完成工程的创建。当然这样创建的工程只是一个空的工程,需要在客户端为空的工程逐个添加文件和目录。
$ cat /repos/project/CVSROOT/passwd
jiangxroot:_passwd_here_:cvsroot
anonymous:_passwd_here_:cvsuser
jiangxin:_passwd_here_:cvs_jiangxin
johnson:_passwd_here_:cvs_johnson
$ cd /repos/project
$ mkdir jiangxin; chown -R cvs_jiangxin:cvsuser jiangxin; chmod -R 2700 jiangxin
$ mkdir johnson; chown -R cvs_johnson:cvsuser johnson; chmod -R 2700 johnson于是创建了两个用户独占的工程。
2.1.4. 用CVS管理文件进行功能扩充
确省安装的CVS的权限仅仅作用于目录,而不能精细到文件级别。而且即使用户只需要拥有文件的只读权限,也要对相应的目录具有写权限,因为需要在目录下创建锁定文件。有一个办法可以避免此问题,即:通过配置文件 CVSROOT/config 的 LockDir 来设置单独的锁定目录,为该单独的锁定目录设置更宽泛的权限控制。
CVS 提供了功能的扩充接口:CVSROOT目录下的管理文件。这些文件提供了相应功能扩充的接口,不但可以完成精细的权限控制,还能完成更加个性化的功能。关于CVSROOT下的脚本,FreeBSD 的源代码就有一个非常好的CVSROOT脚本,可供我们参照:
Setting up a CVS repository - the FreeBSD way。我们可以参照这个指南,定制我们自己的CVSROOT脚本。
下载 FreeBSD 的 CVSROOT 脚本,可以以匿名用户连接到 FreeBSD 的 CVS 服务器:
$ cvs -d :pserver:[email protected]:2401/home/ncvs login # 输入密码 anoncvs
$ cvs -d :pserver:[email protected]:2401/home/ncvs co CVSROOT-src
下载获得 FreeBSD 的 CVSROOT 代码后,需要进行相应的定制,然后才能 checkin 到自己的 CVSROOT 目录中。这个补丁是我对其的修改和定制:patch.txt。
定制过程:
-
升级CVS
确认安装的CVS服务器版本,要高于 1.11.2 。因为我们要用到在版本 1.11.2 才出现的功能:能够在检查commit log 后重新读入 commit log,以实现对 commit log 的格式化。
-
定制 PERL 模块 cfg.pm
文件 CVSROOT/cfg.pm, 是 perl脚本的核心包,对其做了一些改动,主要是添加了禁止某些用户发送 Email功能;还增加了部分子过程,部分是从原 log_acum.pl 中移动过来,目的将这些函数设置为模块内部的函数便于其它需要发送邮件的脚本调用,如脚本 log_accum.pl 和 tagcheck 都需要使用这些新增子过程。
对脚本 CVSROOT/cfg.pm 的改动如下:
diff -u -r1.1 -r1.2
--- cfg.pm 14 Aug 2003 10:00:53 -0000 1.1
+++ cfg.pm 15 Aug 2003 01:44:20 -0000 1.2
@@ -17,9 +17,10 @@
$ADD_TO_LINE $AVAIL_FILE $CHECK_HEADERS $COMMITCHECK_EXTRA
@COMMIT_HOSTS $COMMITTER $DEBUG $DIFF_BLOCK_TOTAL_LINES $EXCLUDE_FILE
$FILE_PREFIX $IDHEADER $LAST_FILE @LOG_FILE_MAP $MAILADDRS $MAILBANNER
- $MAILCMD $MAIL_BRANCH_HDR $MAIL_ON_DIR_CREATION $MAIL_TRANSFORM
+ $MAILCMD $MAIL_BRANCH_HDR @MAIL_MAP $MAIL_ON_DIR_CREATION $MAIL_TRANSFORM
$MINCVSVERSION $MAX_DIFF_SIZE $NO_DOS_LINEBREAKS $PID $PROG_CVS
$PROG_MV %TEMPLATE_HEADERS $TMPDIR $UNEXPAND_RCSID $WARN_HEADERS
+ $BADSENDER_FILE
);
my $CVSROOT = $ENV{'CVSROOT'} || die "Can't determine \$CVSROOT!";
@@ -52,7 +53,7 @@
$PROG_MV = '/bin/mv'; # mv(1)
# The username of the committer.
-$COMMITTER = $ENV{"LOGNAME"} || $ENV{'USER'} || getlogin
+$COMMITTER = $ENV{"CVS_USER"} || $ENV{"LOGNAME"} || $ENV{'USER'} || getlogin
|| (getpwuid($<))[0] || sprintf("uid#%d",$<);
@@ -83,6 +84,7 @@
# commit to what.
$AVAIL_FILE = "$CVSROOT/CVSROOT/avail";
+$BADSENDER_FILE = "$CVSROOT/CVSROOT/blocksender";
################
### logcheck ###
@@ -208,6 +210,10 @@
'other' => '.*'
);
+@MAIL_MAP = (
+ 'nobody' => '.*'
+);
+
# Include diffs of not greater than this size in kbytes in the
# commit mail for each file modified. (0 = off).
$MAX_DIFF_SIZE = 0;
@@ -270,6 +276,64 @@
return @output;
};
+
+############################################################
+#
+# Subroutines
+#
+############################################################
+
+# !!! Mailing-list and commitlog history file mappings here !!!
+# This needs pulling out as a configuration block somewhere so
+# that others can easily change it.
+
+sub get_log_name {
+ my $dir = shift; # Directory name
+
+
+ for my $i (0 .. ($#cfg::LOG_FILE_MAP - 1) / 2) {
+ my $log = $cfg::LOG_FILE_MAP[$i * 2];
+ my $pattern = $cfg::LOG_FILE_MAP[$i * 2 + 1];
+
+ return $log if $dir =~ /$pattern/;
+ }
+
+ return 'other';
+}
+
+sub get_mail_name {
+ my $dir = shift; # Directory name
+ my $CVSROOT = $ENV{'CVSROOT'};
+ $dir =~ s,^$CVSROOT[/]?,,g;
+ $dir .= "/" unless $dir =~ /\/$/;
+
+ for my $i (0 .. ($#cfg::MAIL_MAP - 1) / 2) {
+ my $email = $cfg::MAIL_MAP[$i * 2];
+ my $pattern = $cfg::MAIL_MAP[$i * 2 + 1];
+ return $email if $dir =~ /$pattern/;
+ }
+
+ return $cfg::MAILADDRS;
+}
+
+
+# do not send email, if committer is in badsender file...
+sub sendmail_acl_check {
+ my $sendmail = 1;
+ if (-e $cfg::BADSENDER_FILE)
+ {
+ open (BADSENDER, $cfg::BADSENDER_FILE) || die "open $cfg::BADSENDER_FILE: $!\n";
+ while (<BADSENDER>) {
+ if ($_ =~ /\b$cfg::COMMITTER\b/i)
+ {
+ $sendmail = 0;
+ last;
+ }
+ }
+ }
+
+ return $sendmail;
+}
######################################################################
# Load the local configuration file, that allows the entries in this增加检查环境变量 CVS_USER。以能够正确反映使用 CVSROOT/password 文件进行身份验证的用户名。
通过文件 $BADSENDER_FILE 设置哪些用户对 CVS 操作不必发送邮件,这个功能可以用于自动编译下的特定用户的CVS操作不必发送邮件。
还在该perl模块中增加了几个过程,供其它程序调用。
文件 CVSROOT/cfg_local.pm 用于对模块 cfg.pm 进行定制:
hash$ diff -u -r1.1 cfg_local.pm
--- cfg_local.pm 14 Aug 2003 10:00:53 -0000 1.1
+++ cfg_local.pm 15 Aug 2003 03:09:39 -0000 1.3
@@ -13,7 +13,7 @@
####################################################################
####################################################################
-$CHECK_HEADERS = 1;
+$CHECK_HEADERS = 0;
$IDHEADER = 'FreeBSD';
$UNEXPAND_RCSID = 1;
@@ -29,25 +29,30 @@
$MAILCMD = "/usr/local/bin/mailsend -H";
$MAIL_BRANCH_HDR = "X-FreeBSD-CVS-Branch";
$ADD_TO_LINE = 0;
-$MAILBANNER = "FreeBSD src repository";
+$MAILBANNER = "My repository";
if (defined $ENV{'CVS_COMMIT_ATTRIB'}) {
my $attrib = $ENV{'CVS_COMMIT_ATTRIB'};
$MAILBANNER .= " ($attrib committer)";
}
+# The minimum version of cvs that we will work with.
+$MINCVSVERSION = "1110200"; # 1.11.2
+
+$MAIL_ON_DIR_CREATION = 0;
# Sanity check to make sure we've been run through the wrapper and are
# now primary group 'ncvs'.
#
-$COMMITCHECK_EXTRA = sub {
- my $GRP=`/usr/bin/id -gn`;
- chomp $GRP;
- unless ( $GRP =~ /^ncvs$/ ) {
- print "You do not have group ncvs (commitcheck)!\n";
- exit 1; # We could return false here. But there's
- # nothing to stop us taking action here instead.
- }
- return 1;
-};
+
+#$COMMITCHECK_EXTRA = sub {
+# my $GRP=`/usr/bin/id -gn`;
+# chomp $GRP;
+# unless ( $GRP =~ /^ncvs$/ ) {
+# print "You do not have group ncvs (commitcheck)!\n";
+# exit 1; # We could return false here. But there's
+# # nothing to stop us taking action here instead.
+# }
+# return 1;
+#};
# Wrap this in a hostname check to prevent mail to the FreeBSD
# list if someone borrows this file and forgets to change it.
@@ -91,6 +96,22 @@
@LOG_FILE_MAP = (
'CVSROOT' => '^CVSROOT/',
'distrib' => '^distrib/',
'doc' => '^doc/',
'ports' => '^ports/',
'www' => '^www/',
'other' => '.*'
);
+# CVSROOT is still shared between src, ports, doc at the moment. projects has
+# its own CVSROOT.
+@MAIL_MAP = (
+ 'maillist1' => '^CVSROOT/',
+ 'maillist2' => '^src/',
+ 'cvsnone' => '.*',
+);
+
+@TAG_MAP = (
+ 'jiangxin' => '^(release|mailstome).*',
+);
+
+# Email addresses of recipients of commit mail.
+$MAILADDRS = 'cvsnone';
+
+
1; # Perl requires all modules to return true. Don't delete!!!!
#endCVS 服务器配置文件:CVSROOT/config
#SystemAuth=no
#LockDir=/var/lock/cvs
#TopLevelAdmin=no
LogHistory=TOFEWGCMAR
RereadLogAfterVerify=always如果设置 SystemAuth=no,则只通过 CVS 提供的身份验证。
可以用来指定单独的锁定目录, 便可以更灵活的设置数据仓库的目录权限。
对所有事件记录日志,亦可写为 LogHistory=all。
启用只有在 1.11.2 版本才具有的 commit log 重写功能。
文件 CVSROOT/cvsignore
设置版本控制过程中,需要忽略的文件。这些文件将不被显示为未知状态("?")。如:
*.db
*.info
*.[Sp]o
*.core
*.aps
*.clw
*.exe
*.ncb
*.obj
*.opt
*.plg
Debug
Release
亦可由每个目录下的文件 .cvsignore 来控制;
对于 WinCVS,则由文件 C:\.cvsignore 来控制,如果将 HOME 路径设置为 C:\ 的话。
文件 CVSROOT/cvswrappers
匹配文件名,并作相应处理。如: -kb 即以二进制方式处理文件。
*.gif -k 'b'
*.GIF -k 'b'
*.jpg -k 'b'
...文件 CVSROOT/modules
设置数据仓库中的模块名,可以通过命令:“cvs co -c”察看当前数据仓库(repository)中包含的模块/工程名称。也可以在调整服务器端目录结构时,设置 modules 来保持和以前设置的兼容性。
CVSROOT CVSROOT
module1 module2 &module3文件 CVSROOT/checkoutlist
列在 checkoutlist 中的文件,在 checkin 后,能够自动在服务器 CVSROOT 目录中重建。
#access
avail
cfg.pm
cfg_local.pm
commit_prep.pl
commitcheck
cvs_acls.pl
exclude
log_accum.pl
logcheck
options
rcstemplate
tagcheck
unwrap
wrapaccess 文件供 FreeBSD 的cvs wrapper程序调用,进行权限控制,如不需要该功能注释掉,忽略该文件。
文件 CVSROOT/notify
当用户设置了 watch 一个文件,可以定制该文件进行控制。在此该文件未被用到。
文件 CVSROOT/commitinfo
Commit 事件要触发三个脚本文件,依次是 commitinfo, verifymsg, loginfo。其中先遍历整个目录树对所有需要 commit 的文件执行 commitinfo文件。再分别针对每一个目录执行 verifymsg, loginfo 脚本。
commitinfo 会调用 commitcheck 脚本,完成的功能:通过用户主机名、用户名来检查权限;确认CVS服务器的版本号不低于某个版本;将遍历目录树的结果(最后一个目录名)记录下来,以便接 下来运行 verifymsg, loginfo的脚本能够确认运行结束等。
相关文件:CVSROOT/commitcheck,CVSROOT/cvs_acls.pl,CVSROOT/avail,CVSROOT/commit_prep.pl,CVSROOT/exclude,CVSROOT/cfg.pm,CVSROOT/cfg_local.pm。
文件 CVSROOT/avail,被脚本 cvs_acls.pl 读取,再被脚本 commitcheck 调用,用以精细控制权限。例如:
group|meisters|peter,jdp,markm,joe
# Pick up the list of bad users from ncvs/CVSROOT/badcommitters See that
# file for details
group|penaltybox|!badcommitters
unavail
avail||CVSROOT
avail||distrib
avail||doc
avail||ports
avail||src
unavail||src/contrib/binutils,src/contrib/file
avail|obrien|src/contrib/binutils,src/contrib/file
unavail||src/contrib/tcpdump
avail|fenner,nectar|src/contrib/tcpdump
avail||www
avail|:meisters
unavail|:penaltybox文件 CVSROOT/verifymsg
用于检查和格式化 commit log。禁止在版本控制提交时,使用空的 commit log。对于 wincvs 在用户不提交 commit log 时,会自动使用“no message”作为commit log。为了禁止该情况发生,需要定制该脚本:
相关文件:CVSROOT/logcheck。
bash$ diff -u -r1.1 logcheck
--- logcheck 14 Aug 2003 10:00:53 -0000 1.1
+++ logcheck 15 Aug 2003 02:01:37 -0000 1.2
@@ -47,8 +47,10 @@
# Remove leading and trailing blank lines. (There will be at most
# one because of the duplicate removal above).
+local $^W = 0;
shift @log_in if $log_in[0] eq "";
pop @log_in if $log_in[-1] eq "";
+local $^W = 1;
# Scan through the commit message looking for templated headers
# as defined in the configuration file, and rcstemplate.
@@ -104,6 +106,9 @@
# completely empty. This is a bug in cvs.
my $log = "@log_in";
die "Log message contains no content!\n" if $log =~ /^\s*$/;
+
+# commit without commit log using WINCVS , will automatically provide commit log as "no message".
+die "Log message contains no content using WINCVS!\n" if $log =~ /^no message$/ or $log =~ /^\.+$/;隐藏语法警告;
禁止其它的无意义的commit log,其中一个是 WINCVS 缺省的 COMMITLOG,一个是本人以前随意的COMMITLOG风格。
文件 CVSROOT/loginfo
将 commit log 分门别类存储在目录 CVSROOT/commitlogs 下,并同时通过邮件外发。为了防止一次事件触发多次的邮件外发,该脚本利用到 commitinfo 的运行结果,只有确认到了目录树的最后,才发送邮件。 模块和存储日志文件以及用户邮件列表在文件CVSROOT/cfg_local.pm中定义。
相关文件:CVSROOT/log_accum.pl,CVSROOT/cfg.pm ,CVSROOT/cfg_local.pm。
文件 CVSROOT/loginfo,调用脚本 log_accum.pl,并传递文件名等参数,用于组织日志和发送邮件。
原 FreeBSD 脚本存在一个 BUG,如果文件名或者目录名中存在空格,脚本运行不正常,出错,导致不能组织和发送邮件,这个 Bug 修正方法如下:
在文件 CVSROOT/loginfo中调用 log_accum.pl 时使用的参数由原来的 %s 修改为 %{,,,s},即加入三个连续的逗号作为分隔符,否则使用空格作为分隔符,难以区分是文件的分隔符还是路径或文件名中的空格。
bash$ tail -1 loginfo
DEFAULT $CVSROOT/CVSROOT/log_accum.pl %{,,,s}对文件 CVSROOT/log_accum.pl 的修正如下:
bash$ diff -u -r1.1 log_accum.pl
--- log_accum.pl 14 Aug 2003 10:00:53 -0000 1.1
+++ log_accum.pl 15 Aug 2003 02:00:35 -0000 1.2
@@ -47,6 +47,7 @@
my $LOG_FILE = "$BASE_FN.log";
my $SUMMARY_FILE = "$BASE_FN.summary";
my $LOGNAMES_FILE = "$BASE_FN.lognames";
+my $MAILNAMES_FILE = "$BASE_FN.mailnames";
my $SUBJ_FILE = "$BASE_FN.subj";
my $TAGS_FILE = "$BASE_FN.tags";
my $DIFF_FILE = "$BASE_FN.diff";
@@ -233,7 +234,8 @@
while (<RCS>) {
if (/^[ \t]*Repository revision/) {
chomp;
- my @revline = split;
+ my @revline = split(/[ \t]+/, $_, 5);
+ shift @revline while($revline[0] eq "");
$revision = $revline[2];
$revline[3] =~ m|^$CVSROOT/+(.*),v$|;
$rcsfile = $1;
@@ -383,20 +385,6 @@
# !!! Mailing-list and commitlog history file mappings here !!!
# This needs pulling out as a configuration block somewhere so
# that others can easily change it.
-sub get_log_name {
- my $dir = shift; # Directory name
-
-
- for my $i (0 .. ($#cfg::LOG_FILE_MAP - 1) / 2) {
- my $log = $cfg::LOG_FILE_MAP[$i * 2];
- my $pattern = $cfg::LOG_FILE_MAP[$i * 2 + 1];
-
- return $log if $dir =~ /$pattern/;
- }
-
- return 'other';
-}
-
sub do_changes_file {
my @text = @_;
@@ -408,11 +396,17 @@
$unique{$category} = 1;
my $changes = "$CVSROOT/CVSROOT/commitlogs/$category";
- open CHANGES, ">>$changes"
- or die "Cannot open $changes.\n";
- print CHANGES map { "$_\n" } @text;
- print CHANGES "\n\n\n";
- close CHANGES;
+ if (open CHANGES, ">>$changes")
+ {
+ print CHANGES map { "$_\n" } @text;
+ print CHANGES "\n\n\n";
+ close CHANGES;
+ }
+ else
+ {
+ print "Cannot open $changes.\n";
+ }
+
}
}
@@ -420,22 +414,29 @@
sub mail_notification {
my @text = @_;
-# This is turned off since the To: lines go overboard.
-# Also it has bit-rotted since, and can't just be switched on again.
-# - but keep it for the time being in case we do something like cvs-stable
-# my @mailaddrs = &read_logfile($LOGNAMES_FILE);
-# print(MAIL 'To: cvs-committers' . $dom . ", cvs-all" . $dom);
-# foreach $line (@mailaddrs) {
-# next if ($unique{$line});
-# $unique{$line} = 1;
-# next if /^cvs-/;
-# print(MAIL ", " . $line . $dom);
-# }
-# print(MAIL "\n");
+ if (! &cfg::sendmail_acl_check())
+ {
+ print "mail sent from $cfg::COMMITTER is blocked.\n";
+ return 0;
+ }
+
+ my %unique = ();
+ my @mailaddrs = &read_logfile($MAILNAMES_FILE);
+ # ok, this is kinda hokey, but I need to break up lines with multiple addresses
+ my $fu = join(" ", @mailaddrs);
+ @mailaddrs = split " ", $fu;
+
+ my $to = "";
+ foreach my $category (@mailaddrs) {
+ next if ($unique{$category});
+ $unique{$category} = 1;
+
+ $to .= " " unless $to eq "";
+ $to .= $category;
+ }
my @email = ();
- my $to = $cfg::MAILADDRS;
print "Mailing the commit message to '$to'.\n";
push @email, "To: $to" if $cfg::ADD_TO_LINE;
@@ -497,10 +498,14 @@
}
# Send the email.
- open MAIL, "| $cfg::MAILCMD $to"
- or die "Please check $cfg::MAILCMD.";
- print MAIL map { "$_\n" } @email;
- close MAIL;
+ if(fork() == 0)
+ {
+ open MAIL, "| $cfg::MAILCMD -F $cfg::COMMITTER $to"
+ or die "Please check $cfg::MAILCMD.";
+ print MAIL map { "$_\n" } @email;
+ close MAIL;
+ }
+ exit(0);
}
@@ -634,8 +639,9 @@
#
# Initialize basic variables
#
+my $separator=",,,";
my $input_params = $ARGV[0];
-my ($directory, @filenames) = split " ", $input_params;
+my ($directory, @filenames) = split / ${separator}/, $input_params;
#@files = split(' ', $input_params);
my @path = split('/', $directory);
@@ -660,8 +666,9 @@
}
# Was used for To: lines, still used for commitlogs naming.
-&append_line($LOGNAMES_FILE, &get_log_name("$directory/"));
-&append_line($SUBJ_FILE, "$directory " . join(" ", sort @filenames));
+&append_line($LOGNAMES_FILE, &cfg::get_log_name("$directory/"));
+&append_line($MAILNAMES_FILE, &cfg::get_mail_name("$directory/"));
+&append_line($SUBJ_FILE, "$directory/(" . join(",", sort @filenames) .") ");
#
# Check for a new directory first. This will always appear as a
@@ -697,7 +704,7 @@
&do_changes_file(@text);
&mail_notification(@text);
- system("/usr/local/bin/awake", $directory);
+ # system("/usr/local/bin/awake", $directory);
&cleanup_tmpfiles();
exit 0;
}
@@ -742,7 +749,28 @@
}
# otherwise collect information about which files changed.
- my @files = split;
+ my @tmpfiles = split;
+ my $strname = "";
+ my @files;
+ while (my $item = shift(@tmpfiles))
+ {
+ if ($strname eq "")
+ {
+ $strname = $item;
+ }
+ else
+ {
+ $strname .= " $item";
+ }
+ for (my $i=0; $i<=$#filenames; $i++)
+ {
+ if ($strname eq $filenames[$i])
+ {
+ push (@files, $strname);
+ $strname = "";
+ }
+ }
+ }
push @{ $changed_files{$tag} }, @files if $state == $STATE_CHANGED;
push @{ $added_files{$tag} }, @files if $state == $STATE_ADDED;
push @{ $removed_files{$tag} }, @files if $state == $STATE_REMOVED;
@@ -896,7 +924,7 @@
&mail_notification(@log_msg);
}
-system("/usr/local/bin/awake", $directory);
+# system("/usr/local/bin/awake", $directory);
&cleanup_tmpfiles();
exit 0;
# EOF文件 CVSROOT/taginfo
在执行 tag/rtag 命令前执行该脚本,如果脚本返回非零值,tag/rtag 动作取消。 相关脚本:CVSROOT/tagcheck。负责对添加/删除 TAG 事件进行控制——允许/禁止/发送邮件。
由于 tag/rtag 事件不象 commit 事件,不是通过多个脚本的配合完成,而是只通过一个脚本 taginfo 完成。这就出现一个问题:如果为一个目录树打上TAG,则可能多次执行脚本,可能要多次触发邮件发送。我的解决办法是,根据TAG进程的 PID 确定在整个过程唯一的文件名,将日志记录到该文件中,taginfo 脚本本身无法知道是否结束,而是系统通过 crontab 定期执行脚本 CVSROOT/checkmailspool.sh来检查是否有完成的 tag 邮件需要外发。
#!/usr/bin/perl -w
#
# Author: Jiang Xin
# Reference: http://www.worldhello.net/
#
use strict;
use lib $ENV{CVSROOT};
use CVSROOT::cfg;
#############################################################
#
# Main Body
#
# TAG add/mov/del repo files...
# $1 $2 $3 $4 ...
#
############################################################
my $tag = shift;
my $action = shift;
my $repos = shift;
my $fileitem = "";
my $filerev= "";
my $filelist = "";
my $uid = $cfg::COMMITTER;
my $userlist = "";
my $pattern = "";
my $permission = 1;
my $to = "";
my $tmpstr = &cfg::get_mail_name($repos);
$tmpstr =~ s/\@/\./g ;
$tmpstr="nobody" unless $tmpstr;
my $MAILFILE = "/var/spool/cvsmail/cvs.tag.$tmpstr.$cfg::PID";
die "Usage: tagcheck tag action repos files...\n" unless $repos;
for my $i (0 .. ($#cfg::TAG_MAP - 1) / 2) {
$userlist = $cfg::TAG_MAP[$i * 2];
$pattern = $cfg::TAG_MAP[$i * 2 + 1];
if ($tag =~ /$pattern/i)
{
if ($userlist =~ /\b$uid\b/i)
{
$permission=1;
last;
}
else
{
$permission=0;
last;
}
}
}
if ($permission == 0)
{
# normal users can not do this.
print STDERR "User \"$cfg::COMMITTER\" canot perform this operation!\n";
print STDERR "Only users: $userlist, can handle tag patterm: \"$pattern\"!\n";
}
while ($fileitem = shift)
{
$filerev = shift;
$filelist = sprintf("%s\t%-24s:\t%s\n", $filelist, $fileitem, $filerev);
}
print "save message in spool `dirname $MAILFILE`...\n";
my @email = ();
if (! -e $MAILFILE )
{
$to = &cfg::get_mail_name($repos);
push @email, "From: $uid<$uid>";
push @email, "To: $to";
$tmpstr = sprintf("Date: %s", `date -R`);
chomp $tmpstr;
push @email, $tmpstr;
if ($permission == 0)
{
push @email, "Subject: cvs tag FAILED! ($action $tag on $repos)";
}
else
{
push @email, "Subject: cvs tag success: $action $tag on $repos";
}
push @email, "";
delete $ENV{'TZ'};
$tmpstr = sprintf("%-11s: %-8s", "Author", $cfg::COMMITTER);
push @email, $tmpstr;
$tmpstr = sprintf("%-11s: %-8s", "Date", `/bin/date +"%Y/%m/%d %H:%M:%S %Z"`);
chomp $tmpstr;
push @email, $tmpstr;
$tmpstr = sprintf("%-11s: %-8s", "Tag", $tag);
push @email, $tmpstr;
$tmpstr = sprintf("%-11s: %-8s", "Operation", $action);
push @email, $tmpstr;
push @email, "";
push @email, " $cfg::MAILBANNER", "" if $cfg::MAILBANNER;
}
if ($permission == 0)
{
push @email, "Permission denied: $action $tag on $repos !";
push @email, "--------------------------------------------------";
}
else
{
$tmpstr = sprintf("%-11s: %-8s", "Repository", $repos);
push @email, $tmpstr;
push @email, $filelist if $filelist;
}
#save mail to spool
open MAIL, ">> $MAILFILE "
or die "Cannot open file $MAILFILE for append.";
print MAIL map { "$_\n" } @email;
close MAIL;
if ($permission == 0)
{
exit 1
}
else
{
exit 0
}确定进程唯一的文件名称;
@cfg::TAG_MAP 数组定义了需要权限控制的 TAG 名称,以及授权人列表。受限的TAG名称对应于软件开发中的里程碑,要严格的权限控制。和该模式匹配的 tag,只能被授权人操作,其它名称的 TAG,所有用户都可以操作。
邮件地址亦从 MAIL_MAP 数组中获取;
文件 CVSROOT/checkmailspool.sh,加入到 crontab 中定期执行,检查 tagcheck 生成的邮件。
#!/bin/sh
# checkmailspool.sh
# Auther: Jiang Xin
#
# $CVSMAILPATH (cvs mail spool) is a spool directory created by user,
# and cvs tag message will store in it.
# run this script timely to check cvsmail spool and send messages...
# please put this script in crontab.
CVSMAILPATH=/var/spool/cvsmail
if [ ! -d $CVSMAILPATH ]; then
mkdir -p $CVSMAILPATH
chmod 777 $CVSMAILPATH
fi
cd $CVSMAILPATH
for i in `ls `; do
pid=`echo $i| sed -e "s/.*\.\([0-9]*\)$/\1/"`
xxx=0
while ps -p $pid>/dev/null; do
xxx=`expr $xxx + 1`
if [ $xxx -gt 10 ]; then
break
fi
sleep 3
echo waiting $pid, $xxx times ...
done
echo -e "\n\n========================================" >> $i
echo -e "End\n" >> $i
cat $i | sendmail -oi -oem -odb -t
rm -f $i
done清除CVSROOT产生的临时文件
以上 CVSROOT 脚步在执行过程中将会在临时目录中产生很多临时文件,如果不加以清理,不但会浪费磁盘空间,更有可能导致发送张冠李戴的错误邮件。在 crontab 中配置每隔一个小时执行一遍以下脚本:
#/bin/sh
cd /tmp
ls \#cvs.files.* | sed -e 's/\#cvs.files.\([0-9]*\)\..*$/\1/g' | sort -u | \
while read xxx; do if ps --pid $xxx>/dev/null 2>&1; then continue; fi ; \
rm -f /tmp/\#cvs.files.$xxx.* ; done2.1.5. 基于 Linux ACL 的权限控制
Linux ACL 的相关资源参见:
-
Linux ACL Homepage
http://acl.bestbits.at/
-
Using ACLs with Fedora Core 2
http://www.vanemery.com/Linux/ACL/linux-acl.html
-
还有我的一个 Wiki 页面 =)
http://jiangxin.worldhello.net/wiki/L/Linux/ACL.htm
为什么要使用 ACL 呢?因为对于一个大的CVS项目,需要对各个模块进行精确的权限控制,如果只靠传统的Unix的目录权限控制 user,group,other 将会大大增进系统管理员的负担,项目的“超级用户”需要同时属于多个用户组,才可以访问所有的模块。
突然有一天,发现权限设置不灵了,经过测试,原来 linux 2.4 内核一个用户最多属于 32 个用户组!看来是另辟蹊径的时候了。久闻 acl 的大名,对于2.4内核是以内核补丁方式存在,2.6内核已经集成进去了,这可真是一个好消息。(注意: 2.6.10 内核的ACL存在一个大BUG,已再 2.6.11-rc4 内核中修正。具体参见我的wiki =)
虽然使用 acl,对于CVS来说,前面提到的CVS权限控制依然试用。即:
-
项目相关的所有用户必须对CVS工程目录以及工程的CVSROOT目录具有 r-x 权限;
-
所有用户对文件 CVSROOT/history, CVSROOT/val-tags, 和目录 CVSROOT/commitlogs/(如果安装了CVSROOT扩展),必须拥有写权限;
-
设置目录的 setgid 位,使用户创建目录的时候,新目录能够保持上一级目录的用户属主;
-
CVSROOT 下的脚本,需要清除其 setuid, setgid 位,否则脚本执行时报错;
-
为工程添加权限时,不要修改 symbol link 文件权限,以免互相覆盖;
例如: 多个项目共享同一个 passwd 文件。执行 setfacl 需要加上 -P 参数;
一个权限设置脚本模块:
#!/bin/sh
# acl.sh version 0.9
SETFACLCMD="setfacl -P"
CVSHOMEDIR=/cvshome
##############################################################################
#Declare Function:
####################################################################
# /etc/group 记录:
# tst_root:x:682:u_tst_root
# tst_doc:x:705:u_tst_doc
# tst_src:x:683:u_tst_src
# tst_src_1:x:706:u_tst_src_1
# tst_src_2:x:707:u_tst_src_2
####################################################################
apply_acl_test()
{
if [ $# -ne 1 ];then
echo format: `basename $0` project
exit 1
fi
project=$1
if [ ! -d $project ]; then
echo "Can not find project: $project."
fi
# tst_root 组可以访问所有模块,包括 CVSROOT 的写权限
$SETFACLCMD -d -R -m g:tst_root:rwx $project
$SETFACLCMD -R -m g:tst_root:rwx $project
# tst_src 组能够访问所有代码、文档
$SETFACLCMD -d -R -m g:tst_src:rwx $project/src $project/doc
$SETFACLCMD -R -m g:tst_src:rwx $project/src $project/doc
# 所有用户均能访问文档
$SETFACLCMD -d -R -m g:tst_src:rwx,g:tst_src_1:rwx,g:tst_src_2:rwx,g:tst_doc:rwx $project/doc
$SETFACLCMD -R -m g:tst_src:rwx,g:tst_src_1:rwx,g:tst_src_2:rwx,g:tst_doc:rwx $project/doc
# 需要权限控制的小组,缺省访问权限
$SETFACLCMD -d -R -m g:tst_src_1:rwx,g:tst_src_2:rwx $project/src
$SETFACLCMD -R -m g:tst_src_1:rwx,g:tst_src_2:rwx $project/src
# 设置敏感模块的权限
$SETFACLCMD -d -R -x g:tst_src_1,g:tst_src_2 $project/src/mod1 $project/src/mod2
$SETFACLCMD -R -x g:tst_src_1,g:tst_src_2 $project/src/mod1 $project/src/mod2
$SETFACLCMD -d -R -m g:tst_src_1:rwx $project/src/mod1
$SETFACLCMD -R -m g:tst_src_1:rwx $project/src/mod1
$SETFACLCMD -d -R -m g:tst_src_2:rwx $project/src/mod2
$SETFACLCMD -R -m g:tst_src_2:rwx $project/src/mod2
# 需要权限控制的小组,需要能够进入 tst/src 目录,但只有 root,src 组能够在 src 下创建目录。
# 如此设置,这些用户不能修改 $project/src 目录下面的文件
#$SETFACLCMD -m g:tst_src_1:r-x,g:tst_src_2:r-x $project/src
# 所有用户组都能够进入(只读访问) tst, tst/CVSROOT 目录,否则无法访问相应模块。但只有 root 组能够在 根 下创建目录和修改 CVSROOT。
$SETFACLCMD -m g:tst_src:r-x,g:tst_src_1:r-x,g:tst_src_2:r-x,g:tst_doc:r-x $project
$SETFACLCMD -R -m g:tst_src:r-x,g:tst_src_1:r-x,g:tst_src_2:r-x,g:tst_doc:r-x $project/CVSROOT
}
apply_acl_other_project()
{
# ... ...
}
#End Declare Function:
##############################################################################
##############################################################################
# MAIN Function Here
##############################################################################
if [ $# -eq 0 ];then
echo format: `basename $0` project ...
exit 1
fi
cd ${CVSHOMEDIR}
while [ $# -gt 0 ]; do
project=$1
if [ ! -d $project ]; then
echo "Can not find project: $project."
shift
continue
fi
##############################################################################
# PRE, 预设置
##############################################################################
echo -n "Begin PRE ACL Setup for project: $project ..."
# 设置 ${CVSHOMEDIR}/$project 下文件权限:rwx------, mask 为 rwx;
# 添加 cvs 超级用户 cvsroot,cvsroot_ro 权限;
$SETFACLCMD -R -m m::rwx,u::rwx,g::---,o::---,d:m::rwx,d:u::rwx,d:g::---,d:o::---,g:cvsroot:rwx,d:g:cvsroot:rwx,g:cvsroot_ro:r-x,d:g:cvsroot_ro:r-x ${CVSHOMEDIR}/$project
# 目录属主被子目录集成
chmod -R g+s ${CVSHOMEDIR}/$project
# 设置 ${CVSHOMEDIR} 的属主,设置为 nobody.cvsnull,注意最好任何用户不属于 cvsnull;
chown -R nobody.cvsnull ${CVSHOMEDIR}/$project
echo " done"
##############################################################################
# 开始项目的权限设置
##############################################################################
echo -n "Begin ACL Setup for project: $project ..."
case "$project" in
test*)
apply_acl_test $project
;;
*)
echo "Unknown project name: $project"
exit 1
;;
esac
echo " done"
##############################################################################
# POST, 后设置
##############################################################################
echo -n "Begin POST ACL Setup for project: $project ..."
# 将 CVSROOT 目录中的需要完全读写的文件,清除 ACL, 并清除组的粘贴位,
### 还要注意 symbol link 文件的权限也需要恢复,如文件 passwd, blocksender...
# 因为 CVSROOT 下脚本需要执行,必须去除其 setgid 位
chmod -R g-s "$project/CVSROOT"
# 由于去除了用户属主的保持位, 当用户checkin CVSROOT时?其它用户可能访问不到,因此需要设置组能够访问
# 不能使用 chmod g+rx , 因为对于设置了 ACL, chmod 是修改其 mask
$SETFACLCMD -R -m m::rwx,u::rwx,g::rwx,o::r-x,d:m::rwx,d:u::rwx,d:g::rwx,d:o::r-x $project/CVSROOT
# val-tags, history 文件需要所有人的读写权限
$SETFACLCMD -b "$project/CVSROOT/val-tags" "$project/CVSROOT/history"
chmod 777 "$project/CVSROOT/val-tags" "$project/CVSROOT/history"
# commitlogs/ 目录要可写
$SETFACLCMD -b -R "$project/CVSROOT/commitlogs/"
chmod -R 777 "$project/CVSROOT/commitlogs/"
echo " done"
shift
done
# $SETFACLCMD -b "/etc/passwd.cvs" "/etc/blocksender"
# chmod 444 "/etc/passwd.cvs" "/etc/blocksender"
echo ""
echo "Make sure every one has access right"
ls -ld ${CVSHOMEDIR}
ls -l "/etc/passwd.cvs" "/etc/blocksender"
相关阅读 更多 +排行榜 更多 +