1.背景
本人團隊原來使用VisualSVN工具搭建svn服務器,隨著時間的增長,windows的磁盤空間越來越大,最后滿了,也不太好擴容(當然如果想擴容,最終肯定是可以實現的)。為了以后更好的svn容量管理,就開始新搭建一臺linux服務器,將svn數據單獨放存儲上,這樣后期擴容就很方便了。
簡單一句就是:從VisualSVN遷移數據至Linux版svn中。
2.原有功能
- 協議為http://方式(web方式或客戶端方式均支持)
- 可在線修改用戶自己口令
- 庫名都是中文的
3.安裝過程
3.1.軟件版本
為支持原有功能,本人先在網上搜索了很多,然后參考linux下apache+SVN搭建完美版進行搭建。
當然本次遷移,本人使用了最新的軟件版本,清單如下:
- Apr: apr-1.5.2和apr-util-1.5.4
- Apache: httpd-2.4.25
- Subversion: subversion-1.9.5
- Zlib: zlib-1.2.11
- Sqlite: sqlite-autoconf-3170000
- Pcre: pcre-8.40
各個軟件包可以在官網下載,如何解壓見參考文。
3.2.軟件編譯參數
上述各個軟件編譯時的參數清單如下(有軟件有安裝順序):
- ./configure --prefix=/svn/bin/apr/
- ./configure --prefix=/svn/bin/apr-util/ --with-apr=/svn/bin/apr/
- ./configure --prefix=/svn/bin/sqlite/
- ./configure --prefix=/svn/bin/zlib/
- ./configure --prefix=/svn/bin/pcre/
- ./configure --prefix=/svn/bin/httpd/ --enable-so --enable-dav --enable-dav-fs --enable-maintainer-mode --enable-deflate=shared --enable-expires=shared --enable-headers=shared --enable-rewrite=shared --enable-static-support --with-mpm=prefork --enable-cache --enable-file-cache --enable-rewrite --enable-ssl --enable-proxy --enable-proxy-http --with-apr=/svn/bin/apr/ --with-apr-util=/svn/bin/apr-util/ --with-sqlite=/svn/bin/sqlite/ --with-pcre=/svn/bin/pcre/
- ./configure --prefix=/svn/bin/svn --with-apr=/svn/bin/apr --with-apr-util=/svn/bin/apr-util/ --with-sqlite=/svn/bin/sqlite/ --with-zlib=/svn/bin/zlib/ --with-apxs=/svn/bin/httpd/bin/apxs --with-apache-libexecdir=/svn/bin/httpd/modules --enable-mod-activation
具體各個參數含義,可通過./configure --help顯示,或者查找官方說明。
安裝及后續配置時建議均使用同一個用戶,如本文的svnapp用戶,就會少很多目錄權限的問題。
3.3.安裝完結果
按照上述安裝完后,如果沒有異常發生,那么應該存在以下重要信息:
- /svn/bin/httpd/modules/目錄下應有mod_dav_svn.so和mod_zuthz_svn.so兩個文件
- /svn/bin/httpd/conf/httpd.conf文件中已經自動LoadModule上面2個*.so文件
4.配置過程
4.1.svn建庫
mkdir -p /svndata/Repositories/測試;
/svn/bin/svn/bin/svnadmin create --pre-1.6-compatible /svndata/Repositories/測試
為了與原有客戶端兼容(原來VisualSVN為1.7版本),需添加--pre-1.6-xx參數或者--pre-1.7-xx參數
此時,【/svndata/Repositories/測試】目錄下應自動生成一些配置文件。
驗證svn是否可用:
將/svndata/Repositories/測試/conf/svnserve.conf中anon-access參數修改為write
然后啟動svn【/svn/bin/svn/bin/svnserve -d -r /svndata/Repositories/】,使用svn客戶端,如TortoiseSVN訪問【svn://IP:9000/測試】應該可以正常訪問,且可以新增文件或文件夾,表示svn使用正常
4.2.svn角色權限配置
參考文linux下apache+SVN搭建完美版中已經完美了。
本文中,用戶及權限文件存放于【/svndata/Repositories/svn_conf/】目錄下,文件名為authz及passwd(密碼為明文,因為svn只支持明文)
然后修改【/svndata/Repositories/測試/conf/svnserve.conf】中對應的密碼及權限文件,可以使用絕對路徑,這樣權限角色及密碼就統一管理了。
如果只是使用一個庫,那么就省去單獨配置,直接使用conf下的配置文件進行配置即可。
4.3.apache配置
/svn/bin/httpd/conf/httpd.conf:
1、Listen 80修改為9000端口(如果使用80端口,則需要root用戶啟動) 2、User daemon及Group daemon分別修改為svnapp和svngrp,即svn建庫用戶與apaache啟動用戶為同一個,就不用chmod命令了 3、ServerName 修改為自己的IP地址 4、文件最后添加一行Include conf/httpd-svn.conf,把所有svn的配置都放在/svn/bin/httpd/conf/httpd-svn.conf文件中,方便管理
然后在conf目錄中touch httpd-svn.conf
此時可以使用命令啟動svn及httpd服務:
- /svn/bin/httpd/bin/apachectl -k start
可以使用ps -fu svnapp查看這些進程。
至此,apache和svn是獨立啟動訪問的,保證都ok后,再往下走。
4.4.關聯apache和svn
/svn/bin/httpd/conf/httpd-svn.conf:
SVNUseUTF8 On
<Location /svn>
DAV svn
SVNParentPath /svndata/Repositories
SVNListParentPath On
SVNIndexXSLT "/svnindex.xsl"
SVNPathAuthz short_circuit
AuthType Basic
AuthName "Subversion Repository"
AuthUserFile /svndata/Repositories/svn_conf/htpasswd
AuthzSVNAccessFile /svndata/Repositories/svn_conf/authz
Require valid-user
</Location>
CustomLog logs/svn_logfile "%t %u %{SVN-ACTION}e" env=SVN-ACTION
大部分指令在參考網頁中已經說明,現對特殊的簡要說明:
- SVNUseUTF8指令是svn 1.8版本以后的新功能,告訴mod_dav_svn用utf8編碼解析URL及文件名。此處若不添加,則可能會報【Can't convert string from 'UTF-8' to natvie encoding】的錯,詳情見why repository name should not be utf-8,此問題本人尋找了好幾天,終于在某個網頁上提及了這個指令,然后報著試一試的想法,果然能解決。
- SVNIndexXSLT指令表示web端訪問時,要采用的樣式,這是一個xml的文檔定義表,即定義目錄名、文件名、上一層等顯示的css樣式,本文使用的代碼見后文。
- CustomLog指令為svn正常訪問的日志重定向,可用可不用
然后使用htpasswd命令創建1個用戶,然后設置好權限authz文件(這些均見參考網頁)。
至此,應該可以使用“ http://IP:9000/svn/測試 ”在瀏覽器中訪問了(可先把SVNIndexXSLT指令注釋掉,再測試)
4.5.Web顯示樣式
這幾個顯示的樣式文件及js文件,均從VisualSVN中復制過來,然后稍做修改而得
/svn/bin/httpd/htdocs/svnindex.xsl(從VisualSVN來,未修改):
<?xml version="1.0"?>
<!-- A sample XML transformation style sheet for displaying the Subversion
directory listing that is generated by mod_dav_svn when the "SVNIndexXSLT"
directive is used. -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="*"/>
<xsl:template match="svn">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>
<xsl:if test="index/@base">
<xsl:text>代碼庫 </xsl:text>
<xsl:value-of select="index/@base" />
<xsl:text>:</xsl:text>
<xsl:value-of select="index/@path"/>
</xsl:if>
<xsl:if test="string-length(index/@base) = 0">
<xsl:text>倉庫列表</xsl:text>
</xsl:if>
</title>
<link rel="stylesheet" type="text/css" href="/svnindex.css"/>
<script language="javascript" type="text/javascript" src="/jquery.js" charset="utf-8"></script>
<script language="javascript" type="text/javascript" src="/svnAuth.js" charset="utf-8"></script>
</head>
<body>
<div class="header" id="header">
<xsl:element name="a">
<xsl:attribute name="id">
<xsl:text>lnkModify</xsl:text>
</xsl:attribute>
<xsl:attribute name="style">
<xsl:text>color:green;text-decoration:none;</xsl:text>
</xsl:attribute>
<xsl:attribute name="href">
<xsl:text>javascript:doChange();void(0);</xsl:text>
</xsl:attribute>
<xsl:text>修改密碼</xsl:text>
</xsl:element>
<xsl:element name="span">
<xsl:text> </xsl:text>
</xsl:element>
<xsl:element name="span">
<xsl:text> </xsl:text>
</xsl:element>
<xsl:element name="a">
<xsl:attribute name="style">
<xsl:text>color:green;text-decoration:none;</xsl:text>
</xsl:attribute>
<xsl:attribute name="href">
<xsl:text>http://www.visualsvn.com/doc/</xsl:text>
</xsl:attribute>
<xsl:text>幫助文檔</xsl:text>
</xsl:element>
</div>
<div class="svn">
<xsl:apply-templates/>
</div>
<div class="footer">
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:text>http://www.visualsvn.com/server/</xsl:text>
</xsl:attribute>
<xsl:text>VisualSVN Server</xsl:text>
</xsl:element>
<xsl:text>   powered by </xsl:text>
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="@href"/>
</xsl:attribute>
<xsl:attribute name="title">
<xsl:text>ver </xsl:text>
<xsl:value-of select="@version"/>
</xsl:attribute>
<xsl:text>Subversion </xsl:text>
</xsl:element>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="index">
<div class="rev">
<xsl:value-of select="@name"/>
<xsl:if test="@base">
<xsl:text>代碼庫 </xsl:text>
<xsl:value-of select="@base" />
<xsl:text>:</xsl:text>
<xsl:value-of select="@path"/>
<xsl:text>  </xsl:text>
<xsl:if test="@rev">
<xsl:text>當前版本 Rev.</xsl:text>
<xsl:value-of select="@rev"/>
</xsl:if>
</xsl:if>
<xsl:if test="string-length(@base) = 0">
<xsl:text>倉庫列表</xsl:text>
</xsl:if>
</div>
<xsl:if test="@base">
<div class="dir">
<xsl:element name="a">
<xsl:attribute name="href">/svn/</xsl:attribute>

<xsl:text>返回倉庫列表</xsl:text>
</xsl:element>
</div>
</xsl:if>
<xsl:apply-templates select="updir"/>
<xsl:apply-templates select="dir"/>
<xsl:apply-templates select="file"/>
</xsl:template>
<xsl:template match="updir">
<div class="dir">
<xsl:element name="a">
<xsl:attribute name="href">..</xsl:attribute>

<xsl:text> </xsl:text>
<xsl:text>..</xsl:text>
</xsl:element>
</div>
</xsl:template>
<xsl:template match="dir">
<div class="dir">
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="@href"/>
</xsl:attribute>

<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>/</xsl:text>
</xsl:element>
</div>
</xsl:template>
<xsl:template match="file">
<div class="file">
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="@href"/>
</xsl:attribute>

<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
</xsl:element>
</div>
</xsl:template>
</xsl:stylesheet>
該文件引用的文件有/svn/bin/httpd/htdocs/jquery.js,直接從官網下載壓縮版的即可。
文件/svn/bin/httpd/htdocs/svnindex.css(從VisualSVN來,未修改):
/* Style sheet for displaying the Subversion directory listing
that is generated by mod_dav_svn and "svnindex.xsl". */
body{
font-family:Verdana, Arial, Helvetica, sans-serif;
font-size:9pt;
background: #ffffff;
margin: 0;
padding: 0;
}
img { border: none; }
a {
color: navy;
}
.header
{
text-align: right;
margin-right: 0.5em;
clear: both;
margin-left: 0.5em;
background: url(header.png) top left no-repeat;
height: 48px;
padding: 6px;
}
.footer {
text-align: right;
margin-top: 1em;
padding: 0.5em 1em 0.5em;
font-size: 80%;
}
.svn {
margin-left: 0.5em;
margin-right: 0.5em;
}
.rev {
margin-right: 3px;
padding-left: 3px;
text-align: left;
font-size: 120%;
}
.dir a {
text-decoration: none;
color: black;
display: block;
}
.dir img { vertical-align: middle }
.file a {
text-decoration: none;
color: black;
display: block;
}
.file {
margin: 3px;
padding: 3px;
background: rgb(95%,95%,95%);
}
.file img { vertical-align: middle }
.file:hover {
margin: 3px;
padding: 3px;
background: rgb(90%,100%,90%);
}
.dir {
margin: 3px;
padding: 3px;
background: rgb(90%,90%,90%);
}
.dir:hover {
margin: 3px;
padding: 3px;
background: rgb(80%,100%,80%);
}
還有文件/svn/bin/httpd/htdocs/svnAuth.js(從VisualSVN來,增加txt = txt.replace(/[\n]/ig, '')語句,增加"&type=CHG"和"&type=NEW"兩條語句):
var svnpassURL="/cgi-bin/svnpass.py";
String.prototype.encodeURI=function() {var rS; rS=escape(this); return rS.replace(/\+/g,"%2B");};
function doChange()
{
if (document.getElementById("creatInfo")) $("#creatInfo").remove();
if (!document.getElementById("pwInfo"))
{
$('<div id="pwInfo" style="background-color:#f3f3f3;padding:0px;"> '
+'用戶名:<input type="text" id="username" size="10" /> '
+'密 碼:<input type="password" id="oldpwd" size="8" /> '
+'新密碼:<input type="password" id="newpwd" size="8" /> '
+'新密碼確認:<input type="password" id="newpwdCfm" size="8" /> '
+'<input type="button" id="btnSubmit" value="確認修改" /> '
+'<input type="button" id="btnCancel" value="取消修改" /> '
+'</div>').insertBefore("#header");
$("#btnSubmit").bind("click", execChange);
$("#btnCancel").bind("click", function() { $("#pwInfo").remove(); } );
}
}
function execChange()
{
var u=function(txt)
{
txt = txt.replace(/[\n]/ig, '')
if (txt=="0")
{
alert("操作成功!");
$("#pwInfo").remove();
}
else
{
alert(txt);
$("#btnSubmit").attr("value", "確認修改").removeAttr("disabled");
}
};
$("#btnSubmit").attr("value", "請稍等...").attr("disabled","disabled");
jQuery.post(svnpassURL,
"username="+$("#username").val().encodeURI()
+"&oldpwd="+$("#oldpwd").val().encodeURI()
+"&newpwd="+$("#newpwd").val().encodeURI()
+"&newpwdcfm="+$("#newpwdCfm").val().encodeURI()
+"&type=CHG",
u)
}
function doCreate()
{
if (document.getElementById("pwInfo")) $("#pwInfo").remove();
if (!document.getElementById("creatInfo"))
{
$('<div id="creatInfo" style="background-color:#D1EDD1;padding:0px;"> '
+'用戶名:<input type="text" id="usernameApply" size="10" /> '
+'登錄密碼:<input type="password" id="pwd" size="8" /> '
+'密碼確認:<input type="password" id="pwdCfm" size="8" /> '
+'<input type="button" id="btnApply" value="確認申請" /> '
+'<input type="button" id="btnApplyCancel" value="取消申請" /> '
+'</div>').insertBefore("#header");
$("#btnApply").bind("click", execCreat);
$("#btnApplyCancel").bind("click", function() { $("#creatInfo").remove(); } );
}
}
function execCreat()
{
var u=function(txt)
{
txt = txt.replace(/[\n]/ig, '')
if (txt=="0")
{
alert("操作成功!");
$("#creatInfo").remove();
}
else
{
alert(txt);
$("#btnApply").attr("value", "確認申請").removeAttr("disabled");
}
};
$("#btnApply").attr("value", "請稍等...").attr("disabled","disabled");
jQuery.post(svnpassURL,
"username="+$("#usernameApply").val().encodeURI()
+"&pwd="+$("#pwd").val().encodeURI()
+"&pwdcfm="+$("#pwdCfm").val().encodeURI(),
+"&type=NEW",
u)
}
$(document).bind('ready',function(){
if (document.location.href.toLowerCase().indexOf("/svnauth/")!=-1)
{
$("#lnkModify").before('<a href="javascript:doCreate();void(0);" style="color:green;text-decoration:none;">創建新用戶</a> ');
}
});
當然,除了這3個文件外,還有圖片,請讀者自行添加即可。
至此,web瀏覽器訪問出來的效果就很好看了。但還無法進行web端修改密碼。
5.實現客戶端修改口令
本人在網上找了很多,大部分是PHP的腳本,還需要安裝PHP,感覺并不太方便,也沒有解釋原理,本人實現效果如下:
現整理下實現原理如下:
- WEB端彈出的密碼框由上面的svnAuth.js動態控制
- 真正實現修改口令的是cgi腳本,本文直接使用python提供
因此,本人就偷懶了下,直接把VisualSVN的前端頁面(如上述的css和js文件)拿過來了,這樣就不用重復造輪子了。
但由于VisualSVN執行的cgi程序svnpass為二進制文件,無法直接放到linux上執行,所以就放棄了。
這里得說下,網上大多使用的是apache2xPasswd和apache2xPasswd.ini文件作為cgi的后臺支持。但本人找了下,好像還沒有apache24Passwd的文件,而且這還是二進制的文件,太方便修改了。所以本人還是自己動手吧,最終發現其實并不復雜。
現直接亮出python腳本:/svn/bin/httpd/cgi-bin/svnpass.py:
#!/usr/bin/python
#-*- coding: UTF-8
import os
import cgi
import subprocess
pwdfile = "/svndata/Repositories/svn_conf/htpasswd"
cmdfile = "/svn/bin/httpd/bin/htpasswd"
errmsg = ""
def run_script(c):
try :
res = subprocess.check_output(c, shell=True)
return True
except Exception as exp:
errmsg = exp.output
return False
def check(n, p):
cmd = "%s -bv %s %s %s" % (cmdfile, pwdfile, n, p)
#print "check: ", cmd
return run_script(cmd)
def setPwd(n, p):
cmd = "%s -D %s %s;%s -b %s %s %s" % (cmdfile, pwdfile, n, cmdfile, pwdfile, n, p)
return run_script(cmd)
def main():
print "Content-type: text/html; charset=utf-8\n"
form = cgi.FieldStorage()
name = form.getvalue("username" , "").strip()
optype = form.getvalue("type" , "").strip()
## create
pwd = form.getvalue("pwd" , "").strip()
pwdcfm = form.getvalue("pwdcfm" , "").strip()
## change
oldpwd = form.getvalue("oldpwd" , "").strip()
newpwd1 = form.getvalue("newpwd" , "").strip()
newpwd2 = form.getvalue("newpwdcfm", "").strip()
if name == "" or optype == "" :
print "缺少參數用戶名或者類型"
return
## create
if optype == "NEW" :
if pwd == "" or pwdcfm == "" or pwd != pwdcfm :
print "密碼不能為空或兩次密碼輸入不一致!"
else :
print ("0" if setPwd(name, pwd) else "設置密碼失敗:" + errmsg)
elif optype == "CHG" :
if oldpwd == "" or newpwd1 == "" or newpwd2 == "" or newpwd1 != newpwd2 :
print "密碼不能為空或兩次密碼輸入不一致!"
else :
if not check(name, oldpwd) :
print "原始密碼驗證失敗"
else :
print ("0" if setPwd(name, newpwd1) else "修改密碼失敗:" + errmsg)
else :
print "類型不正確!"
## for key in os.environ.keys():
## print "%s = %s<br/>" % (key, os.environ[key])
main()
此腳本就是當前端svnAuth.js執行jquery.post()方法時,異步的返回是否修改口令成功,如果返回"0",表示成功,返回其它字符表示出錯了。當然這種前后臺接口是最low的,但對于svn這種工具,也沒有必要花心思去設計那么完美的接口,比如什么json、restfull之類的東東。
總結起來核心點是:
- 使用/svn/bin/httpd/bin/htpasswd命令行工具進行驗證、修改用戶口令
那么也就是說大家熟悉哪門語言或者說linux服務器上支持哪門語言,就可以直接寫這樣類似的腳本。本人當前機器剛好已經自帶了python2.7,所以就直接用python去寫了。
然后設置一下svnpass.py的執行權限為+x。
至此,所有的配置就已經完成了。
經過這快2周的調試,總算弄明白這svn通過http方式訪問的原理,而且用戶能自己修改口令。其實我真希望svn官網能直接提供這種支持,讓用戶使用起來更加方便。