From 2742ab1a83674734ac2fc9d65b454b74a72cf0df Mon Sep 17 00:00:00 2001 From: mingzhenliu Date: Mon, 18 Mar 2019 17:43:59 +0800 Subject: [PATCH 001/119] init WeBase --- .gitmodules | 12 +++ README.md | 39 +++++++ architecture.png | Bin 0 -> 15978 bytes install.md | 264 +++++++++++++++++++++++++++++++++++++++++++++ webase-front | 1 + webase-node-mgr | 1 + webase-transcation | 1 + webase-web | 1 + 8 files changed, 319 insertions(+) create mode 100644 .gitmodules create mode 100644 README.md create mode 100644 architecture.png create mode 100644 install.md create mode 160000 webase-front create mode 160000 webase-node-mgr create mode 160000 webase-transcation create mode 160000 webase-web diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2604c38 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "webase-front"] + path = webase-front + url = https://github.com/WeBankFinTech/webase-front.git +[submodule "webase-web"] + path = webase-web + url = https://github.com/WeBankFinTech/webase-web.git +[submodule "webase-node-mgr"] + path = webase-node-mgr + url = https://github.com/WeBankFinTech/webase-node-mgr +[submodule "webase-transcation"] + path = webase-transcation + url = https://github.com/WeBankFinTech/webase-transcation diff --git a/README.md b/README.md new file mode 100644 index 0000000..6482228 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# 目录 +> * [介绍](#chapter-1) +> * [整体架构](#chapter-2) +> * [各子系统介绍](#chapter-3) +> * [应用开发步骤](#chapter-4) + +# 1. 介绍 +WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和底层之间搭建的一套通用组件,可以屏蔽区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率。包含了节点前置、节点管理、上链代理和Web管理平台等子系统。开发者搭建完底层节点后,部署WeBASE,可大幅提升应用层的开发效率。 + +# 2. 整体架构 + +[图片] + +# 3. 各子系统介绍 +**1 webase-front (节点前置)** +集成web3jsdk;以HTTP接口的形式,和节点进行交互,如获取节点快高,部署合约,发送交易等;内置内存数据库,采集节点健康度数据;内置web控制台,实现节点的可视化操作;上报节点数据到指定服务。 + +**2 webase-transcation(交易上链代理)** +接收交易请求,缓存,异步上链。 + +**3 webase-node-mgr(节点管理)** +管理各个节点的状态;接收节点上报的数据,并存储;对区块链的数据进行统计、分析;交易审计;处理前端页面所有;web请求;私钥管理;管理区块链上所有的合约; + +**4 webase-web(区块链管理平台)** +数据概览,可以查看机构、节点、合约、区块和交易详情; +节点管理:查看区块链上所有节点的状态; +合约管理:编辑、编译、部署、调试、测试合约; +私钥管理:管理各用户的公私钥; +系统监控:错误日志查看、各节点的监控数据查看; +交易审计:异常交易事后审计; + +# 4. 应用开发步骤 +1 部署WeBASE。 + +2 登录WeBASE管理平台,开发智能合约,编译、部署、测试合约。 + +3 配置私钥。 + +4 根据协议规范,组装HTTP包,发送交易。 \ No newline at end of file diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf1f7e4813ad5f0a2a6f75d50c54594eb365247 GIT binary patch literal 15978 zcmdUWcUV(Rw{JjD1PmZZkq9D!4G4%r=mKH`0YRlVBT;$)>4XkZY5J-ZsVY@^3q6!b zQA&^wA%uW{^j<^4-Kg((+WpSC=brn|eI9sr_TDqI%B)%CH?v>pYTrD@e3BUi0v)@3 z>$)BYM2`l6=%`GGfhSKj`UimvovYqWHBf#hWCpllc%ph&6$C1ZX4$i11n!R_ZyC9Q zKw#X#KV6Gc)?*MzY5(?h)%%|23q+OHxrgsnmQ6BhtgG(Euf~w`^fI*KT|UQNVY$#F zc0qq?K`%q|n`B4AR5=5;8iub}tR$^MEby4U;bBo$hQ|5;e?R+EGq+;wXEss{x_5lX zkV|FOGp*fTdmFD8D31<-KqtuTpn&&CCeXFJFa}VnD1;uQ%L%3fWgP|N=nYW^fovOv zK%lGtqh|T@eK`gRJW%te#?IoMsF<5DhKgd(*CEH{wqn2DJ5+HQ`s(es$~vv6llAYs z0@LnyQmtKjLHX@0XVYC4p>IANMp~PnzVOb~KOS;5;L;lX?d(KK+m9?d(|DfN9~)C- z32OUS>QqtHyTZ=t=7VvDS+|c}$?JXi%_+4BdprbZ)hM(^&guwgP|v<2w-vc^9R?AF zTowe{@hZOk{MMDktMl(0P1mJFCs54J4`C>ZF|3R+h@EQmK%awQ;fx_$& zaqcfh$h%=dJ#*;lPL~!8(PRHrrLt@rJL-uSq>N9}^ZbJg)3oE8B}dN~b$L+>D+xsI z1xNiSNxPd^0@#G}uVK2MAI^163S~i;7fdk3ybqJJ%U3@;;ik4n#5C3uCY^f`^=~n@Z%rr5`W=C%1B7q~^({bv;Zu=_CR@!oz)#l}-)rzvag+wj#{!*vD zs$=c*XEA(vUiaijkA%GiCJe4p-EfKOqae{OHsm<69#PvVW^6%#COCZJ|504n%fDA8 z&i~BIbGV$$hsDM`yjqhi_e*Dp1DzR8jLC@=!%YObyqO&q22t_%(ffeD>$N3sp0tedM3HT^ z5hgqYSE(f1#)0~qK)fbQ@n?%W`Tmb30fB@^BR#VlQRf%c^RNtOA)7BzVhxWrOOyx7 zKm4@X-=fMTMwX*zO*;>1x%&y$&BrsQ8XCBJPs7XjXWvg2x3EP<`sQi~-rQOUkyDao zIsOkK+2KP*WYJ4ieYU%ZBUO1rkMZs5Bk?cysOmYq9wxQP;bA!Y=F7cvo$Ah*lqX95 zYdjRgpvn0s(ul>yafp5Az?F=xau4-80`|kxE{T+_jp7ARFKLrt-|Q`eSS=c4?dm|; zWr!1+7dMMg3f9W_j=!$g8428UINT5OtRhR&h?FP~gLwtUl76nXb$Lk)t^$OCjS%ZV&!-y>sHM$^;C)Jy~=t+Dp zJ{dVkoYE$(W}5#D8*a#Hulk}KV|kAfAKkXMXP1OicgoleGRE2~fE;qvHt`q7_L)kf=RXzNCbBx}Pae^^1i>or*^{WK@}CB5xu$0z0{Ih;)x>$s zPm{8CD%_a#0$Jh1ajWeLs+Gl~gqXX`g02NjME9GYTI_-I_+?#RaaJz!%WkI**Wd8w z8C9fNaxbk|`B)e`3icYK1|5shpK)aBlyc~Zt?U^7^M-tL=FVntznyFefZ>B}LtB)`g)`Fg2$|KHn#zi?ak}v+-5#A#Qk6Pw+^&X8p zBK_P5Hi8OFKl*B6Y=B4X4LtT;ODN90i})>QbTPwr6S{wIVSRA8D|aSa6q2v@Z}o60 zYg1_HwB!SKt2`*fsdmf*A<4SyCR80pbi?JZjpR*d!d^@$&Nt!0bN65br)~nxpxi-l zf7cCz94*j(QdJUpHTGRjT^_+@vqX;6QoiR~&`cQPukGAP$ss*%5@sXymMy8gwe2KR zJCxnf`qB~!{WOtKY|Li4&jwsep==*r2A_HKkHr8+5JYY9td1t$MPKPBXd0(|q~?eZ zs{=?%ilsE7;@P}Rlf;CUl;L*`po#}eI$Ik0uv{9((RCO~*-Y`YTL;ocsnNB=SvRkn9uu1KreZ>5*yJDI%$|E*%$vs@Tm-3deA;Zb0 zc4%8^FomYPH1Hkkq$06a#kEyFcYl8{a?)FkR5bkHRCt5>yW0dv$$Xwho#7u}F-yM_ z;=cXyjd|hcegira4UE}Y6X@dyWD?PTJoHjb_7#2>I}Z3PqxmOFH@wyEh_j%hqZ80vS$at3A+Pr-$tXCsQYEz1mSA(LkKQJNHF@y? ziOo*EHQ1nU-@$*&0$fc}v>!+kD9>ReM3xS8g9CPG8|9*`GVp3I?YZd>qw%?ZeR#OH z@@Ae>-D;I{F*rgVgBRY#{|-bEQd4Hn9)v_ol!|}LWOlg8HS>Z^uJqEVQm6tOI*BJj zj(b=0(&R`A&M%|zyI|p8Vi5O3x(o^PcL$NWIWc*TLz>g>ZrUD=R|pxzSg%X@2|VM!9Z$bYUM{DX6I`PC^)5fNqEXi@%2a3Wm_H#vAB_{6eRk-vgO=n; z;|xx)NfdFQ;1oETm$ElM92XG3xpdrW-R0x{zsfHBH>tiZFs;B(M<2I9-(^U;)m4)6 z&nabqwZ4a`^>8A<32s-ZibM%uFv^wmbmeGy(L>UewcW0#Auh^*8~dZnd5RHUFRAKB z9QyV87B0R@l(#JS7L5-pUv4dQ9yL)XD+mUnC%FUxMn9vsEQ=(&EBf9|maFlE`F?Xn zDy^6nD*hw4+uzx8ozKwi7o@|$Cf~0($Zl@PZ%qd zTa%I1vMZMb24){wBzNy-8?q$oiWAzZoWUwHB7rpVc3*cvM^ofXFP9m`oja(>8VzJ5~; z&RMSqgA8=zW>3fN%E=T)4i*qPAz+3@zUR|H|0)Y~9l}?3N;oIW2^thQ0V(j`b}?O& z($St8PyxQcE07ZCt87k+`FbybE*P!%&SjB*;&*oaE8D#T3vqKjNUDD&gvY^F#9z7e zuYp3;4P~ckm*o!9Jl%_A_Wq9F$uy88|DSbRUY%?sg1Sq)jT0#%&rzZOX@Y-#IM%|i z(hhm|AG!TYQHTr6|LvV$+xm|2$qCe!&45$-Ud+re6PoKTAgpw=l|1>?M=x@~@1N`e zeSp;R zI(akiHwtN(IKmWZo@2hh7`p(Jf^&!n9KeOfS|i>j0o;(^vsm*y@la ztw$|9brwV>B5yg@AnU76`EdU15KI(>=s}>6LO+xfeeF)LGeG7?=%N0+rMs!J{*qD1 zTd#q(yOY0Ot^hA5ftTX`2R$RsLG;89dag9%6I9}Ez|x+Y!z_v8ktsmONMN#SkO!0g z@7V=qbx1(4-W#@S<4r7gd#>E=DSd!xm3Nh?M)K;M1l1dND9teeWwY#Ai1t~akhT;3 z|LR)4RZ&UmPtH>DU@OQWfb!L0iML=3VTz+mhfGT;EwjzTa~(LM{-Im z0yg=ux@fhwjWnPtbSS$+FO~08tIYlOw@Wt>wH^uHZwdrwu}8D7rDUsG*x-f%fia9|TG?p<5(5L^f5TSJ1TSZosI6Wt(SbcdML zW`!ceu}nh2DdD#ISX>t8FBZOge8n6^cqAy0*$o5!Jd;}Y!tQQe-WKrwI_#IY~- z6(Ml(^l3*ypZK#O1o3tI1;+rhh16Ffl@LU;)W_C|)SG+uc+|(aCisl4XY5n{E2iIEP z9ha>%n>ITOzn#ha4PvIv`wW|5tVVylbk8$WEoL<6O7D%B8?YabxUL1L+X`z6LtLbo zPuB~Ty-w!MMKk~Mn z|4ED%?1w!{LwDuSo#{wvO%aF-hZo2FJy4A^q7o433^eGaV0|*s>ou~q={i||4|H-A zR&pH>kTX~ok_{WlZsOLt zN6|c)OnFTcC^yKG0OqEfQH)YpMH$dycJ*Icl&_yz2i(ghH9zZfS2>l zb0b&_9C^8z@><2I!y8QLS>3gA9jM$dtV?p}i;ll}D%wvx5b5cR4z`pk4m8k$DHh}1 zI0P){p1_CT&rN(f*V2zF7}eT`2Fz>JE`?&t?5nTBtIT2rz3I&a*eUcRY}ujaE*H12 zynW?x?XBvvWMM*h|3&NCwo!-0rD`j?r$eafcE2x}RqItk&&A!{R|S{E3FXOqwCNKl zBYN`k_;BzwSYqY7#kJxV_VUf#>9z@~BPEY#X&19On2R&y{;}I=%{kodpdR>vsOR~z zg^e*gLz}hT+mVt?lgoE%iTLS3HH{c*xq!dvs_0lG=;2|5N+Nh9#dVW9i}Mcc?0wYH z)YXMO+Zm60D7c0(QGJp!tL8PIWb7fRtq4^*rG%}UNGV5F_!H=r-BVJYwP!3Xi8RSr z<~~uf@D3XA%N=nzr0!GG3qK#3@!o?lsMT}g9y`NF}=OtM}G(stFSWs4b#>vj)Zhcbt&w=AyT;MgjzmiPz z-bg6Co+VHm73@1wzv{G4nqphSZ{{pqx@~7E>p*I8rXY8{zEZqw{acis7RJAu=lhXo zFpJ95q5G|CkUV7p&aJKPM^wrFwPfg!p-p71Lz1@i^J=LfGSR7yx?<8Ps839GSW3k_rc_ifwHDuX$lFEsQF&n!MEK?O zx|_)*a?kz5Rqv4t0{AmvwPIVBXV*bYxTbbXXTSmkuBNN?bZ#@pT<=@R&&VD#Y| z&*&x4q-}}(vb`_PO8Pd(W?vfx+>LB zC1PV_TGK4Y9scp;z7%-Q`P9V4zR=ucw^vo21pv)NFyoxv#OD8K@|>d>~@Zj`sX zS4!>YHa{cG-BgMRCS_PoaOV0}tEn1ARn=ubB<4pKJzdk4#jKUqWap77-QUzPvRi7C&xB? zltHBQI4feX!5dlxl4ke@&Jgd9y)xmo-xPwV$z1shTPRs2owK&Vo8Fa86&Iql9zrB6;}>C@;Kg)D3E3Z%ySl3%I$2Tb2)N>>i^< z5vNYztfsrxbD!+e%=K~wo8RMhy7TW24gTyc4=(aZqXFJTNq~ZlcnD1xn87~uu&e<0 z&7B0CiD>_8P82?+8oPUPPJYiOE-z={rcM&^Yck<;Pc4*_F7&w&*I)W<19~jPA{p-rS-; zST z#@eVERwK-x05iKbK=3u$;q>kzVK=}kzm0CS(aOEM()auKi;WKm8&;mwi348H8jIUB z0j&Ord}W7Yj(`FpWey85v9Prf%$Q%AY}&OFn`Uw4DY)upQ*fp18Ns-KZ>{WY(L+nv ze4_3S%djkSm`Wadct@zRAuCA)!3rVcc z)8z>iF~KgH_#1w!#(Ds#@-v){?D>s6r(R{&0=PZQeqkxgL39V>6C+oKKeBYor{80_ zMr(-Fc3^d#JX4ELVTxHhYbU5?L6>ntxrx7`>Xg&`*{?OfqFq139ojMp3-?T&*k@cC69rgt({s5g$%vANZ6)HizT;-u!F5aBZM-LScm~S zTi<4(nT%Z4Jh>uL6=+fTRPAwJ!Fp)5?GBZ?K-r|uEbSv<={kxAnx}sUY_cz(`$gUi zidV`s3V7$0ERJ8$mt7qNeve zar2bj95!Pz^ix3`gnNRitY~pthyKku?y@TXzKMro$!xrBJKg8{DzeL#pS9VI_O=Xc zD1_oZG1k7Gbr4IQweP`DwvpzrT#EiRl+sPbn}C{?gR#23&n0_Lg>;AnkE$9RsUmbh zqLNO`lUnk2&76D`CPuT%Sqw_@KlIxRLtoi(Q<&g#7Tyt`bIk50Ddk$JZ>1x*-|i}j z7`hMaugecRwfnK-Cyz6sWj*XclZTW9pBKm$2u6osazb!{50zk{xxI^vO9?Gammhq% zopNLz8uneD;+l5tDs>TROK~V&Qt)g^*mOGowBWj;pk&NiddnNzCf86LJo=PzhW}vO|==TwC5bF(#;26(9yOlC|Z`e1qX_A zKSa^Rh~FSuV}=`Cs*`42ZsZ)}N?(Jb)25L`#y@(Scu6i)8H=>j@y|uVjyQ_}d23A( zSA=n>FeB~?`vfV8fhSs1HreFsLE5BSa&#Tw@9y@C@I05O zVlgBGHu2%7;z?CsXbpy)u>x-5iVwr9qC#JeF0Ec}>s@lF3pW*Gfhiar{A$_F z)2V$JbLD&sMmX5_KFprkvIoQX0gN8%bUXCeb0n-~Kwa8Xy?k0;BX4pL zf1n?o&p9o&_Y4#u0_HP6vlXgOR+4V`te(!xVK#Yya;S6H%AwXqM;ccy;KopW;^)$L zRVJkvUrrPP#vBP=Yb+L)l}j!1ikZv{Mojwhburt)wOG(naGp7*7zcs69G#>E z!NY|aQ_(irQ+=w^3kI9U&ZWnc*|G(N&1IQ+@Dm5-_z=IIbVu|^;PjW&w!>Tb$kM|; zl;SVn)eKY|@4$Qq(dFxdzNA^}s!IyT!Ege^cHg%T*3>Q z!s7$D>3RpTgF@)P4h8?b91C4zbX12a2CFenONGTX0$d0|l2tzht?{aBk$nRR>|lJV zZiCmas8H_!_-@_*QT6wqky|-{apgYM4(_d~?5|B(&?YTjdTG;UQfM{!>1 z3jm3Gcf8Sxlxt;+U^@z+8u#xop;0Aa+3`we0l-anr_F*^yi>PNsq0XotwhhpxXH=b z#l0U=55dvvNU4maV4u4Vu3Jn<#M ziUC)*PX%b`3IJ{F_U{>h##{glYAI$$PwhF{T0Feq8z1_A@%d` zkgX*OGCoiH*(=ib_wf58eaYLL0CIep1yzDZ91AqzJm@XDi(u$-KwFs5Rbz0P&JypEbz zWlj4y)j8RLWcae(%GM?d;U0@onLQc%dU`cqivE%56v(hdu9hPzdiOeP{d=-CC%7_8 z?nL%^?Um3I8q9_z&y02|{TqaWtbC`Wuw1y}qiECn?I)Y($q;%Y-)r(%)gy(76^$Au zYv=T{*hG&xyg>7V_gQ5{)a_g^t>cCE0D5&IR0t*XqjFT~#te5e;eM+*^9^P0iF zs)|H@I&nVl5mQi6?V!88wCItKDq;!3tr{Blo}*oXA=8{b zv$we?ixA${a9GG!uyv$y$~*J7O)zcK6D}9Afq2&^PDxK;`L+E;dn? zbRp^i8+yOn0bb8LyV~@t10QhU$i)4fY|l*Q`Vm>vCpG=>2Yfsk(w zcx3-v$9NUDSWQ=k1Fssf7VW_gEa+c690>D=$=@<_TJ(ooxWf7!S3navnc+t#><(h!2b%iL zL=0X)!m3{~>p)At)ZN__LDob3>tW2=c_pm>G^x1#1&C~ywPzKI4;cPw>k8rdVwWu?ldId6p-`k z5hw2>{0;MR8qVur?(?_z46-@E03P1aaYWF!aA!Om>K^M=AY?mlV1X*z!3oj|38Oa! zQKc*|a)Xf-ESI>JZN4aK^KEp_h7lRO6Bt_r{d>_tuE+k9<=6 zcC#Nb>b^M1SEdqLT~Wm;K5OkO;gh8S(rUY*NnWiiV0A|Q9>ETOa`VMUu9D{~j?yph zUA(*!CGBtE-G1Pyp*%~^CssHP_s9bQL|r-*grsY<5;_Cx5K?SX7-aI~SRDWBCM~~o zDap=usb)>P*3|Ud^OhEx=MffAtO$hr41zTU-p>v)TWb+jNk*qGwDD-Jvwp^b^yg9{x_S5|N3ODWNUP@ok+PIja{VOQtjmiH-QSwCOJMF}rrGE?E8rt#ev{xK&{L(}%0k3&$&Q z8^=PwZ9bvfa_}Uew%F!Ow(=Kt2}7Ckr9_AKnTU43L{G=!P66}L{Nce<-lE@cp8x)v zl4IvY`ca6{)31n3W2;Oks4T{zT=W{|P?x&J&u{eOmgnsl3&@kRxmSQAv(0E+%>$HT z@xs%)$qLkwG%Pc^P;5YP3t+OsC^{B6JX;T(JUTc$i_}A>i?wcqwu#O#tLpV7f*;9g9vy zG~GQthw+Yg?*b0Z4&i}Avu@9nJ~diB;l51RR1HvI^1#j6?214dkDLAGaZ1^II5s>- zl1nuUe1=0?@7A}VZx0&D2ghPhbR9s=Jns?5!3so&g#uv1-@U`t0u~aQK=sVVwaaiR z&F_yMlh|T=aQ-Wf_%rpdBdP9|--3-~d#|=xN2}a2d8hdJh9H#tV=_B<4a<5MTk5aF zh7QIzwO_aRaQ%Ny7G*rA3X3=)+wPML~A5X>UK?Ti#|PL2<(pWt;XLiry$(&UBiL_E9{@k|0Q@Z;4V z&=5o{$CNp36r;%JoV;N3)&7KEXH_rg97UU)+x%qI_XRn~qZq0lm;T36&opZ;&5u01 z^kY3FH3^$4!}WB0)4g7f$rb<#rJoP2o@*pqY0LMO>nHPqJ-mZP8j5jqGi+!R;@Aa9 zqu3w)M)2I3+B-NtA=&Er;c=%wkU!H0|2H1S3T)tueyH^PR2=y!fMUfBs(o2D6^G8}hd5KKn$)y{(KsHgAwohMK#QrE|v$%j zXkYi^Z)-O#ny~JTAiO_jRwWTW!1BvXUkX5HS_;Eio?qaNp=iAQ@-KGs-w87R!x;7d zsC!piEYt&jqvwvjGfn-)_+H&XY__!e=LZM=>E|t$2tVq^KnIV7Z_y5rC%G>WJxEGV z1^xF{7O{Ahec!b=K3}C*j6^amN1SQbI2{qdqq+nXp`%0^P&@XQprzi# zQK(xa53*$A51@Zx^#Wz7j^uk#D3_0V+sT&j?jQDK22R=VT^|eA!~vjE+D<+ zcd99>R-#4tqvOQe_anM^Hx53xsa8?h9t#G}PWhHpLt|dVf2ruuIwScG06gGQ!o1SH zQWTz&fR!btV2+>TOU@=$k0E;OtSc2Sa+KBNK4h;_Wi%5i*R zr6`i=qw|bBLeva#SQ|)u^Tp_m7)qF)atxm-Y>K^IrPr#ddYRs``xshC86F4Jzw%Lh zjrms;_=K()uYcmdMdt!kD5O(spz~1}Vs(7;6_n?LI5Am)8w|%tbsTP%c{C+;eLEa4 z)BJ=D9?z7rvb`-$Z^vK|AOWb8d3E`n#LSJS;YJPXcPF>w*FQiw|&7HWL# z0A8|KqnV-vn-^dsDr)eMRz=8-0Ij+=yd|$@FoO@${kMD+5GoghV%^5IE9^cQ1HP;EVHuKz86BASkeS=+rL`OO_ zv%*7a*ykcQGE*w}9&NJ!6o$~7-trZOVYj8$jc}&SjvRX*-O}T6aYNx0{e_p3Jb?g~ z@?LKKIhb!PNL&y)8(RhUj0U^=k+`dP-V?H3E8dqZl_dMU?|3QR2cG>>BT5b z+|23{uRhMIN=tFd#rge;R%~1#0L=iSs13%|$6`ekMJq&r_p>c=G)0kI`Z*xaFunHP zO<}_e_ex?TgRfdDBMi6g?z(r{eRnNxU0(){t*6fgoD`BypWb5k_#I{^xYjZBiuBbL zqsH}VsTsWrXgWw0_y8}!H%3CDBSW(pB~FF8g<#ed!ZM8oz?q7oSmbImztj3?igCMp z^JRF@*+RbqrqlRpw@1t^Ky4~uIBNNCh(>-TsiNa?r{e+T==)VmQn%f9InM)UK9O`63?q-iSH#D+cjBZ}FUOw*_f!do+~8Fb z4ejMZ{+S)@_UnDQvwmk4{KR~|?cch56_OEOFSQTXG@gEozxI2YZtaekGb7ZyqwluF zYG^ZleQXPh*l2dwrb5MLN?sIo8p;CL2~ue0_+lJiLlHpfIJk#LvzY<=?1ESO&PPa>;9ZblTo-5zSX`c2B)xIV(EYO*f zNcB~}=3U|514x_atzYs~Z|vRG(|mN2ji$`OHZoEO5L!a6Wn!Gh6w7gV;R zZCc_kZ(GK16Mn0VlSOPwhL%_4^3y>Z&7Z4GV3@*-^0Q24{3UMhM}guHVByovn~%&m zHXmV@dqpN|{Pq2uI1E(|e3G6e059{#B87i8Mc+p=y|Xzisb&#^V|p<|z-7B@q=t|2 ze30mXG;f|!Y)-jg+3XD;`rvdi6Pqr{^6G%)7>kU^)L43x=Y&THsPY6)Ta&%T2zP_T zaF-Tb7UJF{e`dZ(%yLf&6OVa^?qZLQCXD}8W~82~2^dPyy7(BACC8UDuQaZO7xI>P zanMhcgmP?lPtzV%x;9$v$$B|k?2cIFs$c|5f}M{ls9B1|Ha^<5xDPrA81K@ID&tN? zT#HeL^OksX{OHyhOdD!~s|31Ugk(UAuKx^o&|r%2&veKzYOH{Y^K2bMkr~g&9z+6; znlk|!?0{_hQBEQ^LG9{BA+=2596!79UC&Ojpm$g;nS*aL2TFzW>8YGKyBVqlU}$46 zfAzV>9qZA9Z-3TgZ{ALuj77NuA*T+KBw@YvVmV&f88)jV1zvXTGHC#vS@+bYa@?XwJUDc)#o>4!!F-_b z-7}>c72%q-9LM1qP&N1nx0M8&zZXnP6}lL{@r5+uY?C4Bs2Lqe(vE0pp@QJRyyH)I z(9#95Q30_4#Lg7HLO`!bTRqpGq$o`YW5N zX|lHr`LgZ9&`{A1A<0K|0f`N2eDi6MshFVjUn|xzfazivr}^lBO&z*qqmYWd{aaB+ zjoYS=+9htxamJ=iVAm^vorO<+iJGnTa)74eZ%YT*xJ%AHEN>E-sepFC-it6uh}gE5 z0c?U;fEWVOy(9r?p7wf_dG+@>a2z)P{%C!t`yYQi{SC3UPY339IOS%Qb{hCT9CTYl L`+B~bWzhcuLP$kP literal 0 HcmV?d00001 diff --git a/install.md b/install.md new file mode 100644 index 0000000..43afea6 --- /dev/null +++ b/install.md @@ -0,0 +1,264 @@ +# 目录 +> * [依赖环境](#chapter-1) +> * [节点管理安装](#chapter-2) +> * [节点前置服务安装](#chapter-3) +> * [web管理平台安装](#chapter-4) + + +本安装文档仅描述了在一台服务器上安装搭建区块链平台的过程,目的是为了让开发者对区块链平台的部署搭建、运行、测试等有个整体的认识。 +如要用于生产环境,部署安装的流程和原理是一样的,但需要更多考虑分布式系统下服务的部署,需要有容错、容灾等的能力。 + +# 1. 依赖环境 +软件 | 软件要求 +------|-------- +gradle | gradle4.9或更高版本(构建工具) +java | 1.8.0_181或更高版本 +mysql版本 | 5.5及以上版本(框架运行依赖) +python | Python2.7 + +运行服务器要求:1台普通安装linux系统的机器即可。 + +# 2. 节点管理安装 +## 2.1. 获取源代码 +``` +git clone git@git.weoa.com:ttip/fisco-bcos-node-mgr.git +``` + +## 2.2 编译代码 +在代码的根目录webcaf-node-mgr执行构建命令: +``` +gradle build +``` +构建完成后,会在/usr/local/app/webcaf-node-mgr目录生成已编译的代码。 + +## 2.3 创建数据库 +### 2.3.1 修改脚本配置 +进入数据库脚本目录 +``` +cd /usr/local/app/webcaf-node-mgr/conf/script +``` +修改数据库连接信息: +``` +修改数据库名称:sed -i "s/fisco-bcos-data/${your_db_name}/g" fisco-bcos.sh +修改数据库用户名:sed -i "s/defaultAccount/${your_db_account}/g" fisco-bcos.sh +修改数据库密码:sed -i "s/defaultPassword/${your_db_password}/g" fisco-bcos.sh +``` +例如:将数据库用户名修改为root,则执行: +``` +sed -i "s/defaultAccount/root/g" fisco-bcos.sh +``` + +### 2.3.2 运行数据库脚本 +执行命令:sh fisco-bcos.sh ${dbIP} ${dbPort} +如: +``` +sh fisco-bcos.sh 127.0.0.1 3306 +``` + +## 2.4 节点服务的配置及启动 +### 2.4.1 服务配置修改 +进入到已编译的代码根目录: +``` +cd /usr/local/app/webcaf-node-mgr/conf +``` +修改服务配置: +``` +修改当前服务端口:sed -i "s/8080/${your_server_port}/g" application.yml +修改数据库IP:sed -i "s/127.0.0.1/${your_db_port}/g" application.yml +修改数据库名称:sed -i "s/fisco-bcos-data/${your_db_name}/g" application.yml +修改数据库用户名:sed -i "s/defaultAccount/${your_db_account}/g" application.yml +修改数据库密码:sed -i "s/defaultPassword/${your_db_password}/g" application.yml +``` + +### 2.4.2 服务启停 +进入到已编译的代码根目录: +``` +cd /usr/local/app/webcaf-node-mgr +``` +启动: +``` +sh start.sh +``` +停止: +``` +sh stop.sh +``` +状态检查: +``` +sh serverStatus.sh +``` +### 2.4.3 查看日志 +全量日志: +``` +/usr/local/app/logs/webcaf-node-mgr/node-mgr.log +``` +错误日志: +``` +/usr/local/app/logs/webcaf-node-mgr/node-mgr-error.log +``` + +## 2.5 初始化基础合约 +### 2.5.1 前提条件 +节点正常运行 +节点管理服务正常运行 +节点前置正常运行 + +### 2.5.2 修改脚本配置 +进入合约脚本目录: +``` +cd /usr/local/app/webcaf-node-mgr/conf/contract +``` +修改配置中的前置服务信息: +``` +修改前置服务IP:sed -i "s/defaultFrontIp /${your_front_ip}/g" contract-init.sh +修改前置服务的端口:sed -i "s/defaultFrontPort /${your_front_port}/g" contract-init.sh +``` +### 2.5.3 运行脚本 +执行命令: +``` +sh contract-init.sh +``` +如果脚本中三个合约部署返回结果的code都是0,则表示合约所有合约都部署成功 + + +# 3. 节点前置服务安装 + +## 3.1 拉取代码 + +执行命令: +``` +git clone http://xxx/webcaf-front.git +``` + +## 3.2 编译代码 + +在代码的根目录webcaf-front执行构建命令: +``` +gradle build +``` +构建完成后,会在根目录webcaf-front下生成已编译的代码目录dist。 + +## 3.3 修改配置 +进入目录: +``` +cd dist/conf +``` +``` +修改当前服务端口:sed -i "s/8081/${your_server_port}/g" application.yml +修改机构名称:sed -i "s/orgTest/${your_org_name}/g" application.yml +修改节点目录:sed -i "s/\/data\/app\/build\/node0/${your_node_dir}/g" application.yml +修改节点管理服务ip端口:sed -i "s/127.0.0.1:8082/${your_ip_port}/g" application.yml +例子(将端口由8081改为8090):sed -i "s/8081/8090/g" application.yml +``` +进入目录: +``` +cd dist/report +``` +``` +修改节点管理服务ip:sed -i "s/127.0.0.1/${your_ip }/g" config.json +修改节点管理服务端口:sed -i "s/8082/${your_ port}/g" config.json +修改节点目录:sed -i "s/\/data\/app\/build\/node0/${your_node_dir}/g" config.json +``` + +## 3.4 服务启停 + +进入到已编译的代码根目录: +```shell +cd dist +``` +```shell +启动:sh start.sh +停止:sh stop.sh +检查:sh status.sh +``` + +## 3.5 查看日志 + +进入到已编译的代码根目录: +``` +cd dist +``` +``` +前置服务日志:tail -f log/webcaf-front.log +web3连接日志:tail -f log/web3sdk.log +report服务日志:tail -f dist/report/log/report.log +``` + +# 4. web管理平台安装 +## 4.1 安装nginx +### 4.1.1安装依赖 +``` +yum -y install gcc pcre-devel zlib-devel openssl openssl-devel +``` +### 4.1.2获取nginx +``` +cd /usr/local +wge t http://nginx.org/download/nginx-1.10.2.tar.gz (版本号可换) +``` +### 4.1.3安装nginx +``` +tar -zxvf nginx-1.10.2.tar.gz +cd nginx-1.10.2 +./configure --prefix=/usr/local/nginx +make +make install +``` + +## 4.2 获取源代码 +``` +cd /usr/local/app +git clone https://github.com/FISCO-BCOS//fisco-bcos-web.git +``` +## 4.3 修改配置 +找到nginx配置文件,使用vim打开,下面标红的注释就是需要配置的位置 +vim nginx.conf +修改配置(以下配置插入到http{}里面) + +``` +upstream node_mgr_server{ + server 127.0.0.1:8082; //配置mgr地址及端口 +} +server { + listen 8088 default_server; //配置服务端口,需要开通网络策略 + server_name 127.0.0.1; //配置服务地址,可以配置为域名 + location / { +root /data/fisco-bcos-web /dist; //静态文件路径,请指向下载代码的dist目录 + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; +location /api { +proxy_pass http:// node_mgr_server /; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + error_page 404 /404.html; + location = /40x.html { + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + } +} +``` + +## 4.4 启动服务 +找到nginx服务目录 +可以使用命令: +whereis nginx +进入目录并启动: +cd /usr/local/nginx +启动命令: +``` +/usr/local/nginx/sbin/nginx +``` +停止命令: +``` +/usr/local/nginx/sbin/nginx –s stop +``` + +## 4.5 打开浏览器 +输入url:上面的ip或域名+端口 +出现页面后输入初始帐号密码:admin/Abcd1234 + diff --git a/webase-front b/webase-front new file mode 160000 index 0000000..e0370d6 --- /dev/null +++ b/webase-front @@ -0,0 +1 @@ +Subproject commit e0370d644d541bcf24d120b8e7cca4667bd2a169 diff --git a/webase-node-mgr b/webase-node-mgr new file mode 160000 index 0000000..49efeca --- /dev/null +++ b/webase-node-mgr @@ -0,0 +1 @@ +Subproject commit 49efeca5ac164b9d66f6e804e5583010e416800c diff --git a/webase-transcation b/webase-transcation new file mode 160000 index 0000000..04b3aa9 --- /dev/null +++ b/webase-transcation @@ -0,0 +1 @@ +Subproject commit 04b3aa9bf4e524c38c4547cae692f7540274c203 diff --git a/webase-web b/webase-web new file mode 160000 index 0000000..0b90711 --- /dev/null +++ b/webase-web @@ -0,0 +1 @@ +Subproject commit 0b90711ef9fe273fc70a3f23faefeed89518d4f4 From 31c8d48b0f6495ef6c508b76cbac40f06a386709 Mon Sep 17 00:00:00 2001 From: mingzhenliu Date: Mon, 18 Mar 2019 17:47:23 +0800 Subject: [PATCH 002/119] add architecture.png to README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6482228..06ebea8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应 # 2. 整体架构 -[图片] +![[架构图]](./architecture.png) # 3. 各子系统介绍 **1 webase-front (节点前置)** From f53b1e495773379bb858aa571672d8ba2e492c5c Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Mon, 18 Mar 2019 17:58:22 +0800 Subject: [PATCH 003/119] Update install.md --- install.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/install.md b/install.md index 43afea6..e1f2894 100644 --- a/install.md +++ b/install.md @@ -21,21 +21,21 @@ python | Python2.7 # 2. 节点管理安装 ## 2.1. 获取源代码 ``` -git clone git@git.weoa.com:ttip/fisco-bcos-node-mgr.git +git clone https://github.com/WeBankFinTech/webase-node-mgr.git ``` ## 2.2 编译代码 -在代码的根目录webcaf-node-mgr执行构建命令: +在代码的根目录webase-node-mgr执行构建命令: ``` gradle build ``` -构建完成后,会在/usr/local/app/webcaf-node-mgr目录生成已编译的代码。 +构建完成后,会在/usr/local/app/webase-node-mgr目录生成已编译的代码。 ## 2.3 创建数据库 ### 2.3.1 修改脚本配置 进入数据库脚本目录 ``` -cd /usr/local/app/webcaf-node-mgr/conf/script +cd /usr/local/app/webase-node-mgr/conf/script ``` 修改数据库连接信息: ``` @@ -59,7 +59,7 @@ sh fisco-bcos.sh 127.0.0.1 3306 ### 2.4.1 服务配置修改 进入到已编译的代码根目录: ``` -cd /usr/local/app/webcaf-node-mgr/conf +cd /usr/local/app/webase-node-mgr/conf ``` 修改服务配置: ``` @@ -73,7 +73,7 @@ cd /usr/local/app/webcaf-node-mgr/conf ### 2.4.2 服务启停 进入到已编译的代码根目录: ``` -cd /usr/local/app/webcaf-node-mgr +cd /usr/local/app/webase-node-mgr ``` 启动: ``` @@ -90,11 +90,11 @@ sh serverStatus.sh ### 2.4.3 查看日志 全量日志: ``` -/usr/local/app/logs/webcaf-node-mgr/node-mgr.log +/usr/local/app/logs/webase-node-mgr/node-mgr.log ``` 错误日志: ``` -/usr/local/app/logs/webcaf-node-mgr/node-mgr-error.log +/usr/local/app/logs/webase-node-mgr/node-mgr-error.log ``` ## 2.5 初始化基础合约 @@ -106,7 +106,7 @@ sh serverStatus.sh ### 2.5.2 修改脚本配置 进入合约脚本目录: ``` -cd /usr/local/app/webcaf-node-mgr/conf/contract +cd /usr/local/app/webase-node-mgr/conf/contract ``` 修改配置中的前置服务信息: ``` @@ -127,16 +127,16 @@ sh contract-init.sh 执行命令: ``` -git clone http://xxx/webcaf-front.git +git clone http://xxx/webase-front.git ``` ## 3.2 编译代码 -在代码的根目录webcaf-front执行构建命令: +在代码的根目录webase-front执行构建命令: ``` gradle build ``` -构建完成后,会在根目录webcaf-front下生成已编译的代码目录dist。 +构建完成后,会在根目录webase-front下生成已编译的代码目录dist。 ## 3.3 修改配置 进入目录: @@ -179,7 +179,7 @@ cd dist cd dist ``` ``` -前置服务日志:tail -f log/webcaf-front.log +前置服务日志:tail -f log/webase-front.log web3连接日志:tail -f log/web3sdk.log report服务日志:tail -f dist/report/log/report.log ``` From 84217b4535186c8b4866f55946b0aa3ec4affd94 Mon Sep 17 00:00:00 2001 From: gongdaxia Date: Wed, 20 Mar 2019 15:07:18 +0800 Subject: [PATCH 004/119] update install.md update nginx config --- install.md | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/install.md b/install.md index e1f2894..a458a64 100644 --- a/install.md +++ b/install.md @@ -207,14 +207,48 @@ make install ## 4.2 获取源代码 ``` cd /usr/local/app -git clone https://github.com/FISCO-BCOS//fisco-bcos-web.git +git clone https://github.com/WeBankFinTech/webase-web.git ``` ## 4.3 修改配置 -找到nginx配置文件,使用vim打开,下面标红的注释就是需要配置的位置 -vim nginx.conf -修改配置(以下配置插入到http{}里面) +在代码库中docs文件下有nginx配置文件,直接可以拿来替换安装的nginx的配置文件nginx.conf; +然后修改nginx.conf; +进入nginx配置文件(这里nginx安装在/usr/local下面,如果这里没找到,可以到/etc下寻找) ``` + cd /usr/local/nginx/conf +``` + +###4.3.1、 修改web服务端口 +``` + sed -i "s/3002/${your_server_port}/g" nginx.conf +``` +例如: +``` + sed -i "s/3002/8080/g" nginx.conf 你修改的服务端口是8080 +``` + +###4.3.2、 修改服务ip +``` +`sed -i "s/ 10.0.0.1 /${your_server_ip}/g" nginx.conf +``` +例如: +``` + sed -i "s/ 10.0.0.1 /192.168.0.1/g" nginx.conf +``` +你修改的服务ip是192.168.0.1,也可以修改成域名 +###4.3.3、 修改静态文件路径 +``` + sed -i "s/\ /data\/webase-web \/dist /${your_file_route}/g" nginx.conf +``` + +###4.3.4、 修改mgr服务ip和端口 +``` +sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf +```` + +服务器已有nginx可按照以下修改, + +```Nginx upstream node_mgr_server{ server 127.0.0.1:8082; //配置mgr地址及端口 } From eef9d456b12f68bc3e9b6d9147caff1dbeed03c0 Mon Sep 17 00:00:00 2001 From: gongdaxia Date: Wed, 20 Mar 2019 15:08:28 +0800 Subject: [PATCH 005/119] update update --- install.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install.md b/install.md index a458a64..3781f74 100644 --- a/install.md +++ b/install.md @@ -218,7 +218,7 @@ git clone https://github.com/WeBankFinTech/webase-web.git cd /usr/local/nginx/conf ``` -###4.3.1、 修改web服务端口 +### 4.3.1 修改web服务端口 ``` sed -i "s/3002/${your_server_port}/g" nginx.conf ``` @@ -227,7 +227,7 @@ git clone https://github.com/WeBankFinTech/webase-web.git sed -i "s/3002/8080/g" nginx.conf 你修改的服务端口是8080 ``` -###4.3.2、 修改服务ip +### 4.3.2 修改服务ip ``` `sed -i "s/ 10.0.0.1 /${your_server_ip}/g" nginx.conf ``` @@ -236,12 +236,12 @@ git clone https://github.com/WeBankFinTech/webase-web.git sed -i "s/ 10.0.0.1 /192.168.0.1/g" nginx.conf ``` 你修改的服务ip是192.168.0.1,也可以修改成域名 -###4.3.3、 修改静态文件路径 +### 4.3.3 修改静态文件路径 ``` sed -i "s/\ /data\/webase-web \/dist /${your_file_route}/g" nginx.conf ``` -###4.3.4、 修改mgr服务ip和端口 +### 4.3.4 修改mgr服务ip和端口 ``` sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf ```` From e3e39d936bb374a0baf2f223bfe1ff4c40cfd49e Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Sat, 23 Mar 2019 19:50:23 +0800 Subject: [PATCH 006/119] Update install.md --- install.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/install.md b/install.md index e1f2894..bea4e22 100644 --- a/install.md +++ b/install.md @@ -5,8 +5,7 @@ > * [web管理平台安装](#chapter-4) -本安装文档仅描述了在一台服务器上安装搭建区块链平台的过程,目的是为了让开发者对区块链平台的部署搭建、运行、测试等有个整体的认识。 -如要用于生产环境,部署安装的流程和原理是一样的,但需要更多考虑分布式系统下服务的部署,需要有容错、容灾等的能力。 +本安装文档仅描述了在一台服务器上安装搭建WeBASE的过程,目的是为了让开发者对WeBASE的部署搭建、运行、测试等有个整体的认识。如要用于生产环境,部署安装的流程和原理是一样的,但需要更多考虑分布式系统下服务的部署,需要有容错、容灾等的能力。 # 1. 依赖环境 软件 | 软件要求 From d7456b3f39ffd5f451c4485c3403378a38d13272 Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Tue, 26 Mar 2019 10:57:29 +0800 Subject: [PATCH 007/119] Update install.md --- install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.md b/install.md index bea4e22..299ace1 100644 --- a/install.md +++ b/install.md @@ -221,7 +221,7 @@ server { listen 8088 default_server; //配置服务端口,需要开通网络策略 server_name 127.0.0.1; //配置服务地址,可以配置为域名 location / { -root /data/fisco-bcos-web /dist; //静态文件路径,请指向下载代码的dist目录 +root /data/fisco-bcos-web/dist; //静态文件路径,请指向下载代码的dist目录 index index.html index.htm; try_files $uri $uri/ /index.html =404; } From 5c5b5af7d0151bf3b43ddcc56b924bd8df4d3b63 Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Tue, 26 Mar 2019 11:11:00 +0800 Subject: [PATCH 008/119] Update install.md --- install.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install.md b/install.md index 299ace1..a357bba 100644 --- a/install.md +++ b/install.md @@ -218,10 +218,10 @@ upstream node_mgr_server{ server 127.0.0.1:8082; //配置mgr地址及端口 } server { - listen 8088 default_server; //配置服务端口,需要开通网络策略 - server_name 127.0.0.1; //配置服务地址,可以配置为域名 + listen 8088 default_server; #配置服务端口,需要开通网络策略 + server_name 127.0.0.1; #配置服务地址,可以配置为域名 location / { -root /data/fisco-bcos-web/dist; //静态文件路径,请指向下载代码的dist目录 +root /data/fisco-bcos-web/dist; #静态文件路径,请指向下载代码的dist目录 index index.html index.htm; try_files $uri $uri/ /index.html =404; } From 93680c22d63a1672b07fa2aba055faee6e4177fa Mon Sep 17 00:00:00 2001 From: gongdaxia Date: Tue, 26 Mar 2019 11:27:44 +0800 Subject: [PATCH 009/119] update nginx ip update nginx ip --- install.md | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/install.md b/install.md index 3781f74..32dd1da 100644 --- a/install.md +++ b/install.md @@ -250,31 +250,27 @@ sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf ```Nginx upstream node_mgr_server{ - server 127.0.0.1:8082; //配置mgr地址及端口 -} -server { - listen 8088 default_server; //配置服务端口,需要开通网络策略 - server_name 127.0.0.1; //配置服务地址,可以配置为域名 - location / { -root /data/fisco-bcos-web /dist; //静态文件路径,请指向下载代码的dist目录 - index index.html index.htm; - try_files $uri $uri/ /index.html =404; - } - # Load configuration files for the default server block. - include /etc/nginx/default.d/*.conf; -location /api { -proxy_pass http:// node_mgr_server /; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + server 10.0.0.1:8083; //步骤三 节点管理服务地址及端口 } - error_page 404 /404.html; - location = /40x.html { - } - error_page 500 502 503 504 /50x.html; - location = /50x.html { - } -} + server { + listen 3002 default_server; //步骤一 前端端口 + server_name 10.0.0.1; //步骤一 前端地址,可配置为域名 + location / { + root /data/webase-web/dist; //步骤二 前端文件路径 + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + location /mgr { + proxy_pass http://node_mgr_server/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } ``` ## 4.4 启动服务 From 0e9f295f99e2f0bd71285e815238110844775f30 Mon Sep 17 00:00:00 2001 From: gonghao Date: Tue, 26 Mar 2019 11:39:09 +0800 Subject: [PATCH 010/119] update nginx config --- install.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/install.md b/install.md index 1b3c8fb..366ffec 100644 --- a/install.md +++ b/install.md @@ -249,30 +249,7 @@ sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf ```Nginx upstream node_mgr_server{ -<<<<<<< HEAD server 10.0.0.1:8083; //步骤三 节点管理服务地址及端口 -======= - server 127.0.0.1:8082; //配置mgr地址及端口 -} -server { - listen 8088 default_server; #配置服务端口,需要开通网络策略 - server_name 127.0.0.1; #配置服务地址,可以配置为域名 - location / { -root /data/fisco-bcos-web/dist; #静态文件路径,请指向下载代码的dist目录 - index index.html index.htm; - try_files $uri $uri/ /index.html =404; - } - # Load configuration files for the default server block. - include /etc/nginx/default.d/*.conf; -location /api { -proxy_pass http:// node_mgr_server /; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - error_page 404 /404.html; - location = /40x.html { ->>>>>>> 942022a2ff7238e045d85e173eba0d5a2513dd73 } server { listen 3002 default_server; //步骤一 前端端口 From ae8bca108f7944cdc0983fc2d702dc8d83cf6e6e Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Thu, 28 Mar 2019 14:30:34 +0800 Subject: [PATCH 011/119] Update install.md --- install.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install.md b/install.md index a357bba..67c631a 100644 --- a/install.md +++ b/install.md @@ -109,8 +109,8 @@ cd /usr/local/app/webase-node-mgr/conf/contract ``` 修改配置中的前置服务信息: ``` -修改前置服务IP:sed -i "s/defaultFrontIp /${your_front_ip}/g" contract-init.sh -修改前置服务的端口:sed -i "s/defaultFrontPort /${your_front_port}/g" contract-init.sh +修改前置服务IP:sed -i "s/defaultFrontIp/${your_front_ip}/g" contract-init.sh +修改前置服务的端口:sed -i "s/defaultFrontPort/${your_front_port}/g" contract-init.sh ``` ### 2.5.3 运行脚本 执行命令: @@ -215,7 +215,7 @@ vim nginx.conf ``` upstream node_mgr_server{ - server 127.0.0.1:8082; //配置mgr地址及端口 + server 127.0.0.1:8082; #配置mgr地址及端口 } server { listen 8088 default_server; #配置服务端口,需要开通网络策略 @@ -228,7 +228,7 @@ root /data/fisco-bcos-web/dist; #静态文件路径,请指向下载代码 # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location /api { -proxy_pass http:// node_mgr_server /; +proxy_pass http://node_mgr_server /; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From 7eb26c1cac09c1d794435540313d07a0879b0b4a Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 2 Apr 2019 00:03:42 +0800 Subject: [PATCH 012/119] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 06ebea8..0692582 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应 集成web3jsdk;以HTTP接口的形式,和节点进行交互,如获取节点快高,部署合约,发送交易等;内置内存数据库,采集节点健康度数据;内置web控制台,实现节点的可视化操作;上报节点数据到指定服务。 **2 webase-transcation(交易上链代理)** -接收交易请求,缓存,异步上链。 +接收交易请求,缓存交易到数据库中,异步上链。可大幅提升吞吐量,解决区块链的tps瓶颈。 **3 webase-node-mgr(节点管理)** -管理各个节点的状态;接收节点上报的数据,并存储;对区块链的数据进行统计、分析;交易审计;处理前端页面所有;web请求;私钥管理;管理区块链上所有的合约; +管理各个节点的状态;接收节点上报的数据,并存储;对区块链的数据进行统计、分析;交易审计;处理前端页面所有web请求;私钥管理;管理区块链上所有的合约; **4 webase-web(区块链管理平台)** 数据概览,可以查看机构、节点、合约、区块和交易详情; @@ -36,4 +36,4 @@ WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应 3 配置私钥。 -4 根据协议规范,组装HTTP包,发送交易。 \ No newline at end of file +4 根据协议规范,组装HTTP包,发送交易。 From 056c48f5f52f69ccedf33e34d26dabc82562876e Mon Sep 17 00:00:00 2001 From: gonghao Date: Tue, 2 Apr 2019 16:20:32 +0800 Subject: [PATCH 013/119] update install.md --- install.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/install.md b/install.md index 366ffec..c7eda87 100644 --- a/install.md +++ b/install.md @@ -205,7 +205,7 @@ make install ## 4.2 获取源代码 ``` -cd /usr/local/app +cd /data git clone https://github.com/WeBankFinTech/webase-web.git ``` ## 4.3 修改配置 @@ -286,6 +286,12 @@ cd /usr/local/nginx ``` /usr/local/nginx/sbin/nginx –s stop ``` +检查nginx是否启动成功 + +``` + ps -ef | grep nginx +``` +观察进程是否起来 ## 4.5 打开浏览器 输入url:上面的ip或域名+端口 From da3974043327c291705e9a4d95d28f7d4f4d5b68 Mon Sep 17 00:00:00 2001 From: gonghao Date: Tue, 2 Apr 2019 16:23:03 +0800 Subject: [PATCH 014/119] update install.md --- install.md | 159 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 109 insertions(+), 50 deletions(-) diff --git a/install.md b/install.md index c7eda87..c3af999 100644 --- a/install.md +++ b/install.md @@ -184,40 +184,33 @@ report服务日志:tail -f dist/report/log/report.log ``` # 4. web管理平台安装 -## 4.1 安装nginx -### 4.1.1安装依赖 -``` -yum -y install gcc pcre-devel zlib-devel openssl openssl-devel -``` -### 4.1.2获取nginx -``` -cd /usr/local -wge t http://nginx.org/download/nginx-1.10.2.tar.gz (版本号可换) -``` -### 4.1.3安装nginx -``` -tar -zxvf nginx-1.10.2.tar.gz -cd nginx-1.10.2 -./configure --prefix=/usr/local/nginx -make -make install -``` +## 4.1 部署前端 +### 4.1.1依赖环境 + +| 环境 | 版本 | +| ------ | --------------- | +| nginx | nginx1.6或以上版本 | + +nginx安装请参考4.2 安装nginx + +### 4.1.2 拉取代码 + +代码可以放在/data下面 +执行命令: + + git clone https://github.com/WeBankFinTech/webase-web.git + +### 4.1.3 修改nginx配置 -## 4.2 获取源代码 -``` -cd /data -git clone https://github.com/WeBankFinTech/webase-web.git -``` -## 4.3 修改配置 在代码库中docs文件下有nginx配置文件,直接可以拿来替换安装的nginx的配置文件nginx.conf; 然后修改nginx.conf; -进入nginx配置文件(这里nginx安装在/usr/local下面,如果这里没找到,可以到/etc下寻找) +进入nginx配置文件(这里nginx安装在/usr/local下面,如果这里没找到,可以到/etc下寻找,如有权限问题,请加上sudo) ``` cd /usr/local/nginx/conf ``` -### 4.3.1 修改web服务端口 +1、 修改web服务端口(端口需要开通策略且不能被占用) ``` sed -i "s/3002/${your_server_port}/g" nginx.conf ``` @@ -226,36 +219,37 @@ git clone https://github.com/WeBankFinTech/webase-web.git sed -i "s/3002/8080/g" nginx.conf 你修改的服务端口是8080 ``` -### 4.3.2 修改服务ip +2、 修改服务ip ``` -`sed -i "s/ 10.0.0.1 /${your_server_ip}/g" nginx.conf + sed -i "s/ 10.0.0.1 /${your_server_ip}/g" nginx.conf ``` 例如: ``` sed -i "s/ 10.0.0.1 /192.168.0.1/g" nginx.conf ``` 你修改的服务ip是192.168.0.1,也可以修改成域名 -### 4.3.3 修改静态文件路径 + +3、 修改静态文件路径(文件需要有权限访问) ``` sed -i "s/\ /data\/webase-web \/dist /${your_file_route}/g" nginx.conf ``` -### 4.3.4 修改mgr服务ip和端口 +4、 修改mgr服务ip和端口 ``` sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf ```` 服务器已有nginx可按照以下修改, - ```Nginx -upstream node_mgr_server{ - server 10.0.0.1:8083; //步骤三 节点管理服务地址及端口 + + upstream node_mgr_server{ + server 10.0.0.1:8083; #步骤三 节点管理服务地址及端口 } server { - listen 3002 default_server; //步骤一 前端端口 + listen 3002 default_server; //步骤一 前端端口(端口需要开通策略且不能被占用) server_name 10.0.0.1; //步骤一 前端地址,可配置为域名 location / { - root /data/webase-web/dist; //步骤二 前端文件路径 + root /data/webase-web/dist; //步骤二 前端文件路径(文件需要有权限访问) index index.html index.htm; try_files $uri $uri/ /index.html =404; } @@ -272,28 +266,93 @@ upstream node_mgr_server{ } ``` -## 4.4 启动服务 -找到nginx服务目录 -可以使用命令: -whereis nginx -进入目录并启动: -cd /usr/local/nginx +## 4.1.3 启动nginx + +(1)、启动nginx。 启动命令: + + /usr/local/nginx/sbin/nginx (nginx下载在/usr/local目录下) + +检查nginx是否启动成功 + ``` -/usr/local/nginx/sbin/nginx + ps -ef | grep nginx ``` -停止命令: +观察进程是否起来 + +启动报错重点排查:日志路径是否正确(error.log和access.log),nginx有没有添加用户权限。 + +(2)、打开页面,页面url是nginx配置的ip和端口。 +例如:上面配置文件的url为 http://10.0.0.1:3002 + +(3)、打开页面后,请找运维提供帐号和密码登录。 + + +## 4.2 安装nginx(可参考[网络教程](http://www.runoob.com/linux/nginx-install-setup.html)) + +### 4.2.1 下载nginx依赖 +在安装nginx前首先要确认系统中安装了gcc、pcre-devel、zlib-devel、openssl-devel。如果没有,请执行命令 + + yum -y install gcc pcre-devel zlib-devel openssl openssl-devel +执行命令时注意权限问题,如遇到,请加上sudo + +#### 4.2.2 下载nginx +nginx下载地址:https://nginx.org/download/(下载最新稳定版本即可) +或者使用命令: + + wget http://nginx.org/download/nginx-1.10.2.tar.gz (版本号可换) +将下载的包移动到/usr/local/下 + +### 4.2.3 安装nginx + +#### 4.2.3.1解压 + tar -zxvf nginx-1.9.9.tar.gz + +#### 4.2.3.2进入nginx目录 + + cd nginx-1.9.9 + +##### 4.2.3.3配置 + + ./configure --prefix=/usr/local/nginx + +#### 4.2.3.4make + + make + make install + +#### 4.2.3.5测试是否安装成功 +使用命令: + + /usr/local/nginx/sbin/nginx –t +正常情况的信息输出: + + nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok + nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful + +### 4.2.4常用nginx命令 + +启动: + ``` -/usr/local/nginx/sbin/nginx –s stop + /usr/local/nginx/sbin/nginx ``` -检查nginx是否启动成功 +停止: ``` - ps -ef | grep nginx + /usr/local/nginx/sbin/nginx -s stop ``` -观察进程是否起来 +重启 +``` + /usr/local/nginx/sbin/nginx -s reload +``` + +### 4.2.5 nginx日志 + +nginx日志一般在nginx安装目录下,例如:/usr/local/nginx/log + +有access.log和error.log两个日志 + + -## 4.5 打开浏览器 -输入url:上面的ip或域名+端口 -出现页面后输入初始帐号密码:admin/Abcd1234 From 880f41bca6c81d4c2c78e9543ef5513496ea47a0 Mon Sep 17 00:00:00 2001 From: gonghao Date: Tue, 2 Apr 2019 16:24:39 +0800 Subject: [PATCH 015/119] update install.md --- install.md | 54 ------------------------------------------------------ 1 file changed, 54 deletions(-) diff --git a/install.md b/install.md index 6214116..5412af9 100644 --- a/install.md +++ b/install.md @@ -184,7 +184,6 @@ report服务日志:tail -f dist/report/log/report.log ``` # 4. web管理平台安装 -<<<<<<< HEAD ## 4.1 部署前端 ### 4.1.1依赖环境 @@ -245,59 +244,6 @@ sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf upstream node_mgr_server{ server 10.0.0.1:8083; #步骤三 节点管理服务地址及端口 -======= -## 4.1 安装nginx -### 4.1.1安装依赖 -``` -yum -y install gcc pcre-devel zlib-devel openssl openssl-devel -``` -### 4.1.2获取nginx -``` -cd /usr/local -wge t http://nginx.org/download/nginx-1.10.2.tar.gz (版本号可换) -``` -### 4.1.3安装nginx -``` -tar -zxvf nginx-1.10.2.tar.gz -cd nginx-1.10.2 -./configure --prefix=/usr/local/nginx -make -make install -``` - -## 4.2 获取源代码 -``` -cd /usr/local/app -git clone https://github.com/FISCO-BCOS//fisco-bcos-web.git -``` -## 4.3 修改配置 -找到nginx配置文件,使用vim打开,下面标红的注释就是需要配置的位置 -vim nginx.conf -修改配置(以下配置插入到http{}里面) - -``` -upstream node_mgr_server{ - server 127.0.0.1:8082; #配置mgr地址及端口 -} -server { - listen 8088 default_server; #配置服务端口,需要开通网络策略 - server_name 127.0.0.1; #配置服务地址,可以配置为域名 - location / { -root /data/fisco-bcos-web/dist; #静态文件路径,请指向下载代码的dist目录 - index index.html index.htm; - try_files $uri $uri/ /index.html =404; - } - # Load configuration files for the default server block. - include /etc/nginx/default.d/*.conf; -location /api { -proxy_pass http://node_mgr_server /; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - error_page 404 /404.html; - location = /40x.html { ->>>>>>> f0d6d63bfcf4b85fe9840bf8f09dbf5f9e368a00 } server { listen 3002 default_server; //步骤一 前端端口(端口需要开通策略且不能被占用) From a5665f655bc535f2797338fc669e72602c7c5b3e Mon Sep 17 00:00:00 2001 From: Sayou1989 <32632184+Sayou1989@users.noreply.github.com> Date: Tue, 2 Apr 2019 16:49:21 +0800 Subject: [PATCH 016/119] change front git url --- install.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/install.md b/install.md index 5412af9..3c231f9 100644 --- a/install.md +++ b/install.md @@ -125,37 +125,39 @@ sh contract-init.sh ## 3.1 拉取代码 执行命令: -``` -git clone http://xxx/webase-front.git +```shell +git clone https://github.com/WeBankFinTech/webase-front.git ``` ## 3.2 编译代码 在代码的根目录webase-front执行构建命令: -``` -gradle build +```shell +gradle build -x test ``` 构建完成后,会在根目录webase-front下生成已编译的代码目录dist。 ## 3.3 修改配置 进入目录: -``` +```shell cd dist/conf ``` ``` 修改当前服务端口:sed -i "s/8081/${your_server_port}/g" application.yml 修改机构名称:sed -i "s/orgTest/${your_org_name}/g" application.yml 修改节点目录:sed -i "s/\/data\/app\/build\/node0/${your_node_dir}/g" application.yml -修改节点管理服务ip端口:sed -i "s/127.0.0.1:8082/${your_ip_port}/g" application.yml +修改节点管理服务ip端口:sed -i "s/10.0.0.1:8082/${your_ip_port}/g" application.yml 例子(将端口由8081改为8090):sed -i "s/8081/8090/g" application.yml ``` +**备注:如果字符串中存在斜杠'/',在前面加单个反斜杠'\\'转义** + 进入目录: -``` +```shell cd dist/report ``` ``` -修改节点管理服务ip:sed -i "s/127.0.0.1/${your_ip }/g" config.json -修改节点管理服务端口:sed -i "s/8082/${your_ port}/g" config.json +修改节点管理服务ip:sed -i "s/10.0.0.1/${your_ip}/g" config.json +修改节点管理服务端口:sed -i "s/8082/${your_port}/g" config.json 修改节点目录:sed -i "s/\/data\/app\/build\/node0/${your_node_dir}/g" config.json ``` @@ -180,7 +182,7 @@ cd dist ``` 前置服务日志:tail -f log/webase-front.log web3连接日志:tail -f log/web3sdk.log -report服务日志:tail -f dist/report/log/report.log +report服务日志:tail -f report/log/report.log ``` # 4. web管理平台安装 From f832da2b05e66ae2381cbd7e305a302f7603b558 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Wed, 3 Apr 2019 11:50:41 +0800 Subject: [PATCH 017/119] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0692582..47515c9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ > * [应用开发步骤](#chapter-4) # 1. 介绍 -WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和底层之间搭建的一套通用组件,可以屏蔽区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率。包含了节点前置、节点管理、上链代理和Web管理平台等子系统。开发者搭建完底层节点后,部署WeBASE,可大幅提升应用层的开发效率。 +WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和底层之间搭建的一套通用组件,围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。可以屏蔽区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率。包含了节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 + +开发者搭建完区块链节点后,部署WeBASE,基于WeBASE开发区块链应用,将会大幅提升效率。 # 2. 整体架构 From 311ffdcef50e65db4a7f4ef805205e7adc6e9ec0 Mon Sep 17 00:00:00 2001 From: dwusiq Date: Wed, 10 Apr 2019 15:37:28 +0800 Subject: [PATCH 018/119] add Hyperlink --- install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.md b/install.md index 3c231f9..0e1ec33 100644 --- a/install.md +++ b/install.md @@ -1,6 +1,6 @@ # 目录 > * [依赖环境](#chapter-1) -> * [节点管理安装](#chapter-2) +> * [节点管理安装](https://github.com/WeBankFinTech/webase-node-mgr/blob/dev-0.5/README.md) > * [节点前置服务安装](#chapter-3) > * [web管理平台安装](#chapter-4) @@ -13,7 +13,7 @@ gradle | gradle4.9或更高版本(构建工具) java | 1.8.0_181或更高版本 mysql版本 | 5.5及以上版本(框架运行依赖) -python | Python2.7 +python | Python2.7 运行服务器要求:1台普通安装linux系统的机器即可。 From 8e8d366ce4ca72a1e2f61f18605c3de30f837193 Mon Sep 17 00:00:00 2001 From: dwusiq Date: Wed, 10 Apr 2019 16:00:13 +0800 Subject: [PATCH 019/119] add Hyperlink --- install.md | 337 +---------------------------------------------------- 1 file changed, 4 insertions(+), 333 deletions(-) diff --git a/install.md b/install.md index 0e1ec33..f6ca3cb 100644 --- a/install.md +++ b/install.md @@ -1,6 +1,6 @@ # 目录 > * [依赖环境](#chapter-1) -> * [节点管理安装](https://github.com/WeBankFinTech/webase-node-mgr/blob/dev-0.5/README.md) +> * [节点管理安装](#chapter-2) > * [节点前置服务安装](#chapter-3) > * [web管理平台安装](#chapter-4) @@ -18,343 +18,14 @@ python | Python2.7 运行服务器要求:1台普通安装linux系统的机器即可。 # 2. 节点管理安装 -## 2.1. 获取源代码 -``` -git clone https://github.com/WeBankFinTech/webase-node-mgr.git -``` - -## 2.2 编译代码 -在代码的根目录webase-node-mgr执行构建命令: -``` -gradle build -``` -构建完成后,会在/usr/local/app/webase-node-mgr目录生成已编译的代码。 - -## 2.3 创建数据库 -### 2.3.1 修改脚本配置 -进入数据库脚本目录 -``` -cd /usr/local/app/webase-node-mgr/conf/script -``` -修改数据库连接信息: -``` -修改数据库名称:sed -i "s/fisco-bcos-data/${your_db_name}/g" fisco-bcos.sh -修改数据库用户名:sed -i "s/defaultAccount/${your_db_account}/g" fisco-bcos.sh -修改数据库密码:sed -i "s/defaultPassword/${your_db_password}/g" fisco-bcos.sh -``` -例如:将数据库用户名修改为root,则执行: -``` -sed -i "s/defaultAccount/root/g" fisco-bcos.sh -``` - -### 2.3.2 运行数据库脚本 -执行命令:sh fisco-bcos.sh ${dbIP} ${dbPort} -如: -``` -sh fisco-bcos.sh 127.0.0.1 3306 -``` - -## 2.4 节点服务的配置及启动 -### 2.4.1 服务配置修改 -进入到已编译的代码根目录: -``` -cd /usr/local/app/webase-node-mgr/conf -``` -修改服务配置: -``` -修改当前服务端口:sed -i "s/8080/${your_server_port}/g" application.yml -修改数据库IP:sed -i "s/127.0.0.1/${your_db_port}/g" application.yml -修改数据库名称:sed -i "s/fisco-bcos-data/${your_db_name}/g" application.yml -修改数据库用户名:sed -i "s/defaultAccount/${your_db_account}/g" application.yml -修改数据库密码:sed -i "s/defaultPassword/${your_db_password}/g" application.yml -``` - -### 2.4.2 服务启停 -进入到已编译的代码根目录: -``` -cd /usr/local/app/webase-node-mgr -``` -启动: -``` -sh start.sh -``` -停止: -``` -sh stop.sh -``` -状态检查: -``` -sh serverStatus.sh -``` -### 2.4.3 查看日志 -全量日志: -``` -/usr/local/app/logs/webase-node-mgr/node-mgr.log -``` -错误日志: -``` -/usr/local/app/logs/webase-node-mgr/node-mgr-error.log -``` - -## 2.5 初始化基础合约 -### 2.5.1 前提条件 -节点正常运行 -节点管理服务正常运行 -节点前置正常运行 - -### 2.5.2 修改脚本配置 -进入合约脚本目录: -``` -cd /usr/local/app/webase-node-mgr/conf/contract -``` -修改配置中的前置服务信息: -``` -修改前置服务IP:sed -i "s/defaultFrontIp/${your_front_ip}/g" contract-init.sh -修改前置服务的端口:sed -i "s/defaultFrontPort/${your_front_port}/g" contract-init.sh -``` -### 2.5.3 运行脚本 -执行命令: -``` -sh contract-init.sh -``` -如果脚本中三个合约部署返回结果的code都是0,则表示合约所有合约都部署成功 +###   请查看[《节点管理安装说明》](https://github.com/WeBankFinTech/webase-node-mgr/blob/dev-0.5/README.md#chapter-3) # 3. 节点前置服务安装 +###   请查看[《节点前置安装说明》](https://github.com/WeBankFinTech/webase-front/tree/dev-0.5#chapter-4) -## 3.1 拉取代码 - -执行命令: -```shell -git clone https://github.com/WeBankFinTech/webase-front.git -``` - -## 3.2 编译代码 - -在代码的根目录webase-front执行构建命令: -```shell -gradle build -x test -``` -构建完成后,会在根目录webase-front下生成已编译的代码目录dist。 - -## 3.3 修改配置 -进入目录: -```shell -cd dist/conf -``` -``` -修改当前服务端口:sed -i "s/8081/${your_server_port}/g" application.yml -修改机构名称:sed -i "s/orgTest/${your_org_name}/g" application.yml -修改节点目录:sed -i "s/\/data\/app\/build\/node0/${your_node_dir}/g" application.yml -修改节点管理服务ip端口:sed -i "s/10.0.0.1:8082/${your_ip_port}/g" application.yml -例子(将端口由8081改为8090):sed -i "s/8081/8090/g" application.yml -``` -**备注:如果字符串中存在斜杠'/',在前面加单个反斜杠'\\'转义** - -进入目录: -```shell -cd dist/report -``` -``` -修改节点管理服务ip:sed -i "s/10.0.0.1/${your_ip}/g" config.json -修改节点管理服务端口:sed -i "s/8082/${your_port}/g" config.json -修改节点目录:sed -i "s/\/data\/app\/build\/node0/${your_node_dir}/g" config.json -``` - -## 3.4 服务启停 - -进入到已编译的代码根目录: -```shell -cd dist -``` -```shell -启动:sh start.sh -停止:sh stop.sh -检查:sh status.sh -``` - -## 3.5 查看日志 - -进入到已编译的代码根目录: -``` -cd dist -``` -``` -前置服务日志:tail -f log/webase-front.log -web3连接日志:tail -f log/web3sdk.log -report服务日志:tail -f report/log/report.log -``` # 4. web管理平台安装 -## 4.1 部署前端 -### 4.1.1依赖环境 - -| 环境 | 版本 | -| ------ | --------------- | -| nginx | nginx1.6或以上版本 | - -nginx安装请参考4.2 安装nginx - -### 4.1.2 拉取代码 - -代码可以放在/data下面 -执行命令: - - git clone https://github.com/WeBankFinTech/webase-web.git - -### 4.1.3 修改nginx配置 - -在代码库中docs文件下有nginx配置文件,直接可以拿来替换安装的nginx的配置文件nginx.conf; -然后修改nginx.conf; - -进入nginx配置文件(这里nginx安装在/usr/local下面,如果这里没找到,可以到/etc下寻找,如有权限问题,请加上sudo) -``` - cd /usr/local/nginx/conf -``` - -1、 修改web服务端口(端口需要开通策略且不能被占用) -``` - sed -i "s/3002/${your_server_port}/g" nginx.conf -``` -例如: -``` - sed -i "s/3002/8080/g" nginx.conf 你修改的服务端口是8080 -``` - -2、 修改服务ip -``` - sed -i "s/ 10.0.0.1 /${your_server_ip}/g" nginx.conf -``` -例如: -``` - sed -i "s/ 10.0.0.1 /192.168.0.1/g" nginx.conf -``` -你修改的服务ip是192.168.0.1,也可以修改成域名 - -3、 修改静态文件路径(文件需要有权限访问) -``` - sed -i "s/\ /data\/webase-web \/dist /${your_file_route}/g" nginx.conf -``` - -4、 修改mgr服务ip和端口 -``` -sed -i "s/ 10.0.0.1:8083 /${your_mgrServer_ipPort}/g" nginx.conf -```` - -服务器已有nginx可按照以下修改, -```Nginx - - upstream node_mgr_server{ - server 10.0.0.1:8083; #步骤三 节点管理服务地址及端口 - } - server { - listen 3002 default_server; //步骤一 前端端口(端口需要开通策略且不能被占用) - server_name 10.0.0.1; //步骤一 前端地址,可配置为域名 - location / { - root /data/webase-web/dist; //步骤二 前端文件路径(文件需要有权限访问) - index index.html index.htm; - try_files $uri $uri/ /index.html =404; - } - - # Load configuration files for the default server block. - include /etc/nginx/default.d/*.conf; - - location /mgr { - proxy_pass http://node_mgr_server/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - } -``` - -## 4.1.3 启动nginx - -(1)、启动nginx。 -启动命令: - - /usr/local/nginx/sbin/nginx (nginx下载在/usr/local目录下) - -检查nginx是否启动成功 - -``` - ps -ef | grep nginx -``` -观察进程是否起来 - -启动报错重点排查:日志路径是否正确(error.log和access.log),nginx有没有添加用户权限。 - -(2)、打开页面,页面url是nginx配置的ip和端口。 -例如:上面配置文件的url为 http://10.0.0.1:3002 - -(3)、打开页面后,请找运维提供帐号和密码登录。 - - -## 4.2 安装nginx(可参考[网络教程](http://www.runoob.com/linux/nginx-install-setup.html)) - -### 4.2.1 下载nginx依赖 -在安装nginx前首先要确认系统中安装了gcc、pcre-devel、zlib-devel、openssl-devel。如果没有,请执行命令 - - yum -y install gcc pcre-devel zlib-devel openssl openssl-devel -执行命令时注意权限问题,如遇到,请加上sudo - -#### 4.2.2 下载nginx -nginx下载地址:https://nginx.org/download/(下载最新稳定版本即可) -或者使用命令: - - wget http://nginx.org/download/nginx-1.10.2.tar.gz (版本号可换) -将下载的包移动到/usr/local/下 - -### 4.2.3 安装nginx - -#### 4.2.3.1解压 - tar -zxvf nginx-1.9.9.tar.gz - -#### 4.2.3.2进入nginx目录 - - cd nginx-1.9.9 - -##### 4.2.3.3配置 - - ./configure --prefix=/usr/local/nginx - -#### 4.2.3.4make - - make - make install - -#### 4.2.3.5测试是否安装成功 -使用命令: - - /usr/local/nginx/sbin/nginx –t -正常情况的信息输出: - - nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok - nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful - -### 4.2.4常用nginx命令 - -启动: - -``` - /usr/local/nginx/sbin/nginx -``` -停止: - -``` - /usr/local/nginx/sbin/nginx -s stop -``` -重启 -``` - /usr/local/nginx/sbin/nginx -s reload -``` - -### 4.2.5 nginx日志 - -nginx日志一般在nginx安装目录下,例如:/usr/local/nginx/log - -有access.log和error.log两个日志 - - +###   请查看[《web管理平台安装说明》](https://github.com/WeBankFinTech/webase-web/blob/dev-0.5/README.md) From 9ecfab9c0f6ce410e37be4d8d20c7be8d287006f Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Wed, 17 Apr 2019 16:47:56 +0800 Subject: [PATCH 020/119] Update README.md --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 47515c9..de79de1 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,70 @@ # 目录 > * [介绍](#chapter-1) -> * [整体架构](#chapter-2) -> * [各子系统介绍](#chapter-3) -> * [应用开发步骤](#chapter-4) +> * [支持平台](#chapter-2) +> * [设计原则](#chapter-3) +> * [整体架构](#chapter-4) +> * [安装说明](#chapter-5) +> * [各子系统介绍](#chapter-6) +> * [应用开发步骤](#chapter-7) -# 1. 介绍 +# 介绍 WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和底层之间搭建的一套通用组件,围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。可以屏蔽区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率。包含了节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 开发者搭建完区块链节点后,部署WeBASE,基于WeBASE开发区块链应用,将会大幅提升效率。 -# 2. 整体架构 +# 支持平台 +目前运行的操作系统平台如下: +Linux + +# 设计原则 +**按需部署** +WeBASE抽象应用开发的诸多共性模块,形成各类服务组件,开发者根据需要部署所需组件。 + +**微服务** +WeWeBASE采用微服务架构,基于spring-boot框架,提供Restful风格接口。 + +**零耦合** +WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务。 + +**可定制** +前端页面往往带有自身的业务属性,因此WeBASE采用前后端分离的技术,便于开发者基于后端接口定制自己的前端页面。 + +# 整体架构 ![[架构图]](./architecture.png) -# 3. 各子系统介绍 -**1 webase-front (节点前置)** + +# 安装说明 +请参考安装说明文档Install.md。 +请参考安装说明文档[install.md](https://github.com/WeBankFinTech/WeBASE/blob/dev/install.md) + + +# 各子系统介绍 +**webase-front (节点前置)** 集成web3jsdk;以HTTP接口的形式,和节点进行交互,如获取节点快高,部署合约,发送交易等;内置内存数据库,采集节点健康度数据;内置web控制台,实现节点的可视化操作;上报节点数据到指定服务。 -**2 webase-transcation(交易上链代理)** +**webase-transcation(交易上链代理)** 接收交易请求,缓存交易到数据库中,异步上链。可大幅提升吞吐量,解决区块链的tps瓶颈。 -**3 webase-node-mgr(节点管理)** +**webase-node-mgr(节点管理)** 管理各个节点的状态;接收节点上报的数据,并存储;对区块链的数据进行统计、分析;交易审计;处理前端页面所有web请求;私钥管理;管理区块链上所有的合约; -**4 webase-web(区块链管理平台)** +**webase-web(区块链管理平台)** 数据概览,可以查看机构、节点、合约、区块和交易详情; 节点管理:查看区块链上所有节点的状态; 合约管理:编辑、编译、部署、调试、测试合约; 私钥管理:管理各用户的公私钥; -系统监控:错误日志查看、各节点的监控数据查看; +系统监控:各节点的监控数据查看; 交易审计:异常交易事后审计; -# 4. 应用开发步骤 +# 应用开发步骤 1 部署WeBASE。 -2 登录WeBASE管理平台,开发智能合约,编译、部署、测试合约。 +2 登录WeBASE管理平台,添加节点信息,私钥信息等。 + +3 开发智能合约,编译、部署、测试合约。 + +4 根据所写合约和交易api的格式,发送交易。 -3 配置私钥。 +5 登录管理平台查看交易详情,查看交易统计信息,在线运维管理 -4 根据协议规范,组装HTTP包,发送交易。 From 847b11a9b767a9a6d7ed75d5915a0b61785ec854 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Wed, 17 Apr 2019 16:48:30 +0800 Subject: [PATCH 021/119] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index de79de1..1ce82d6 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,8 @@ WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务 # 安装说明 -请参考安装说明文档Install.md。 请参考安装说明文档[install.md](https://github.com/WeBankFinTech/WeBASE/blob/dev/install.md) - # 各子系统介绍 **webase-front (节点前置)** 集成web3jsdk;以HTTP接口的形式,和节点进行交互,如获取节点快高,部署合约,发送交易等;内置内存数据库,采集节点健康度数据;内置web控制台,实现节点的可视化操作;上报节点数据到指定服务。 From 27f2e80aa8f869e75a387639ce242901bb0cb583 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 21 May 2019 16:32:55 +0800 Subject: [PATCH 022/119] Update README.md --- README.md | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1ce82d6..cc369ad 100644 --- a/README.md +++ b/README.md @@ -38,22 +38,39 @@ WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务 请参考安装说明文档[install.md](https://github.com/WeBankFinTech/WeBASE/blob/dev/install.md) # 各子系统介绍 -**webase-front (节点前置)** -集成web3jsdk;以HTTP接口的形式,和节点进行交互,如获取节点快高,部署合约,发送交易等;内置内存数据库,采集节点健康度数据;内置web控制台,实现节点的可视化操作;上报节点数据到指定服务。 - -**webase-transcation(交易上链代理)** -接收交易请求,缓存交易到数据库中,异步上链。可大幅提升吞吐量,解决区块链的tps瓶颈。 - -**webase-node-mgr(节点管理)** -管理各个节点的状态;接收节点上报的数据,并存储;对区块链的数据进行统计、分析;交易审计;处理前端页面所有web请求;私钥管理;管理区块链上所有的合约; - -**webase-web(区块链管理平台)** -数据概览,可以查看机构、节点、合约、区块和交易详情; -节点管理:查看区块链上所有节点的状态; -合约管理:编辑、编译、部署、调试、测试合约; -私钥管理:管理各用户的公私钥; -系统监控:各节点的监控数据查看; -交易审计:异常交易事后审计; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
子系统名功能介绍
webase-front节点前置集成web3jsdk,提供restful风格的接口,客户端可以使用http的形式和节点进行交互,如获取节点快高,部署合约,发送交易等。内置内存数据库,采集节点健康度数据。内置web控制台,实现节点的可视化操作。
webase-transcation交易上链代理接收交易请求,缓存交易到数据库中,异步上链。可大幅提升吞吐量,解决区块链的tps瓶颈。
webase-node-mgr节点管理处理前端页面所有web请求,管理各个节点的状态,管理链上所有智能合约,对区块链的数据进行统计、分析,对异常交易的审计,私钥管理等;
webase-webWeBASE管理平台数据概览:可以查看机构、节点、合约、区块和交易详情。节点管理:查看区块链上所有节点的状态。合约管理:编辑、编译、部署、调试、测试合约。私钥管理:管理各用户的公私钥。系统监控:各节点的监控数据查看。交易审计:异常交易事后审计。
+ # 应用开发步骤 1 部署WeBASE。 From 0a551ffd60078e8e1797d5a78fef0d5b1e2e0f5a Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Fri, 14 Jun 2019 15:31:40 +0800 Subject: [PATCH 023/119] Update README.md --- README.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cc369ad..197eebf 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,11 @@ -# 目录 -> * [介绍](#chapter-1) -> * [支持平台](#chapter-2) -> * [设计原则](#chapter-3) -> * [整体架构](#chapter-4) -> * [安装说明](#chapter-5) -> * [各子系统介绍](#chapter-6) -> * [应用开发步骤](#chapter-7) - -# 介绍 +# 介绍 WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和底层之间搭建的一套通用组件,围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。可以屏蔽区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率。包含了节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 开发者搭建完区块链节点后,部署WeBASE,基于WeBASE开发区块链应用,将会大幅提升效率。 -# 支持平台 -目前运行的操作系统平台如下: -Linux -# 设计原则 + +# 设计原则 **按需部署** WeBASE抽象应用开发的诸多共性模块,形成各类服务组件,开发者根据需要部署所需组件。 @@ -29,15 +18,15 @@ WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务 **可定制** 前端页面往往带有自身的业务属性,因此WeBASE采用前后端分离的技术,便于开发者基于后端接口定制自己的前端页面。 -# 整体架构 +# 整体架构 ![[架构图]](./architecture.png) -# 安装说明 +# 安装说明 请参考安装说明文档[install.md](https://github.com/WeBankFinTech/WeBASE/blob/dev/install.md) -# 各子系统介绍 +# 各子系统介绍 @@ -72,7 +61,7 @@ WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务
-# 应用开发步骤 +# 应用开发步骤 1 部署WeBASE。 2 登录WeBASE管理平台,添加节点信息,私钥信息等。 @@ -83,3 +72,10 @@ WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务 5 登录管理平台查看交易详情,查看交易统计信息,在线运维管理 +# 贡献说明 +请阅读我们的贡献[文档](https://github.com/WeBankFinTech/WeBASE/blob/master/CONTRIBUTING.md),了解如何贡献代码,并提交你的贡献。 + +希望在您的参与下,WeBASE会越来越好! + +# 社区 +- 联系我们:webase@webank.com From 9c0c475d0bb30a25629c9ff564aef92f29e32db4 Mon Sep 17 00:00:00 2001 From: mingzhenliu Date: Fri, 14 Jun 2019 15:58:34 +0800 Subject: [PATCH 024/119] delete --- .gitmodules | 12 ------------ webase-front | 1 - webase-node-mgr | 1 - webase-transcation | 1 - webase-web | 1 - 5 files changed, 16 deletions(-) delete mode 160000 webase-front delete mode 160000 webase-node-mgr delete mode 160000 webase-transcation delete mode 160000 webase-web diff --git a/.gitmodules b/.gitmodules index 2604c38..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +0,0 @@ -[submodule "webase-front"] - path = webase-front - url = https://github.com/WeBankFinTech/webase-front.git -[submodule "webase-web"] - path = webase-web - url = https://github.com/WeBankFinTech/webase-web.git -[submodule "webase-node-mgr"] - path = webase-node-mgr - url = https://github.com/WeBankFinTech/webase-node-mgr -[submodule "webase-transcation"] - path = webase-transcation - url = https://github.com/WeBankFinTech/webase-transcation diff --git a/webase-front b/webase-front deleted file mode 160000 index e0370d6..0000000 --- a/webase-front +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0370d644d541bcf24d120b8e7cca4667bd2a169 diff --git a/webase-node-mgr b/webase-node-mgr deleted file mode 160000 index 49efeca..0000000 --- a/webase-node-mgr +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 49efeca5ac164b9d66f6e804e5583010e416800c diff --git a/webase-transcation b/webase-transcation deleted file mode 160000 index 04b3aa9..0000000 --- a/webase-transcation +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 04b3aa9bf4e524c38c4547cae692f7540274c203 diff --git a/webase-web b/webase-web deleted file mode 160000 index 0b90711..0000000 --- a/webase-web +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0b90711ef9fe273fc70a3f23faefeed89518d4f4 From 75b38589ca369bbe5e42ec2fcf265504915924db Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Fri, 14 Jun 2019 16:38:55 +0800 Subject: [PATCH 025/119] Update install.md --- install.md | 302 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 282 insertions(+), 20 deletions(-) diff --git a/install.md b/install.md index f6ca3cb..21d4e01 100644 --- a/install.md +++ b/install.md @@ -1,31 +1,293 @@ -# 目录 -> * [依赖环境](#chapter-1) -> * [节点管理安装](#chapter-2) -> * [节点前置服务安装](#chapter-3) -> * [web管理平台安装](#chapter-4) +# 一键部署说明 +[TOC] -本安装文档仅描述了在一台服务器上安装搭建WeBASE的过程,目的是为了让开发者对WeBASE的部署搭建、运行、测试等有个整体的认识。如要用于生产环境,部署安装的流程和原理是一样的,但需要更多考虑分布式系统下服务的部署,需要有容错、容灾等的能力。 +​ 一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置(webase-front)、节点管理(webase-node-mgr)、管理平台(webase-web)。 -# 1. 依赖环境 -软件 | 软件要求 -------|-------- -gradle | gradle4.9或更高版本(构建工具) -java | 1.8.0_181或更高版本 -mysql版本 | 5.5及以上版本(框架运行依赖) -python | Python2.7 +​ 部署脚本会拉取相关安装包进行部署(需保持网络畅通),重复部署可根据提示进行相关操作。 -运行服务器要求:1台普通安装linux系统的机器即可。 +## 1、前提条件 -# 2. 节点管理安装 -###   请查看[《节点管理安装说明》](https://github.com/WeBankFinTech/webase-node-mgr/blob/dev-0.5/README.md#chapter-3) +| 环境 | 版本 | +| ------ | ---------------------- | +| Java | jdk1.8.0_121或以上版本 | +| python | 2.7 | +| MySQL-python | 1.2.5 | +| mysql | mysql-5.6或以上版本 | +**备注:** 安装说明请参看 [附录7](#7附录) -# 3. 节点前置服务安装 -###   请查看[《节点前置安装说明》](https://github.com/WeBankFinTech/webase-front/tree/dev-0.5#chapter-4) +## 2、拉取部署脚本 +获取部署安装包: +```shell +wget https://github.com/mingzhenliu/sss/releases/download/111/webase-deploy.zip +``` +解压安装包: +```shell +unzip webase-deploy.zip +``` +进入目录: +```shell +cd webase-deploy +``` -# 4. web管理平台安装 -###   请查看[《web管理平台安装说明》](https://github.com/WeBankFinTech/webase-web/blob/dev-0.5/README.md) +## 3、修改配置(没有变化可以不修改) +① 可以使用以下命令修改,也可以直接修改文件(vi common.properties) +② 数据库需要提前安装(数据库安装请参看 [附录7.4](#74-数据库部署)) + +③ 服务端口不能小于1024 + +```shell +数据库ip:sed -i "s%127.0.0.1%${your_db_ip}%g" common.properties +数据库端口:sed -i "s%3306%${your_db_port}%g" common.properties +数据库用户名:sed -i "s%dbUsername%${your_db_account}%g" common.properties +数据库密码:sed -i "s%dbPassword%${your_db_password}%g" common.properties +数据库名称:sed -i "s%db_mgr%${your_db_name}%g" common.properties + +web服务端口:sed -i "s%8080%${your_web_port}%g" common.properties +mgr服务端口:sed -i "s%8081%${your_mgr_port}%g" common.properties +front服务端口:sed -i "s%8082%${your_front_port}%g" common.properties + +节点fisco版本:sed -i "s%2.0.0-rc2%${your_fisco_version}%g" common.properties +节点安装个数:sed -i "s%nodeCounts%${your_node_counts}%g" common.properties +节点p2p端口:sed -i "s%30300%${your_p2p_port}%g" common.properties +节点channel端口:sed -i "s%20200%${your_channel_port}%g" common.properties +节点rpc端口:sed -i "s%8545%${your_rpc_port}%g" common.properties +前置h2数据库名:sed -i "s%/db_front%${your_dist_dir}%g" common.properties +前置要监控的磁盘路径:sed -i "s%/data%${your_dist_dir}%g" common.properties + +例子(将磁盘路径由/data改为/home):sed -i "s%/data%/home%g" common.properties +``` + +## 4、部署 +部署所有服务: +```shell +python deploy.py startAll +``` +停止所有服务: +```shell +python deploy.py stopAll +``` +单独启停命令和说明可查看帮助: +```shell +python deploy.py help +``` + +**备注:** 部署过程出现问题可以查看 [常见问题8](#8常见问题) + +## 5、访问 + +管理平台: + +``` +http://{deployIP}:{webPort} +``` + +节点前置控制台: + +``` +http://{deployIP}:{frontPort}/webase-front +``` + +**备注:**部署服务器IP和相关服务端口需对应修改 + +## 6、日志路径 + +``` +部署日志:log/ +节点日志:nodes/127.0.0.1/node*/log/ +web服务日志:webase-web/log/ +mgr服务日志:webase-node-mgr/logs/ +front服务日志:webase-front/log/ +``` + +## 7、附录 + +### 7.1 Java环境部署 + +此处给出简单步骤,供快速查阅。更详细的步骤,请参考[官网](http://www.oracle.com/technetwork/java/javase/downloads/index.html)。 + +(1)从[官网](http://www.oracle.com/technetwork/java/javase/downloads/index.html)下载对应版本的java安装包,并解压到相应目录 + +```shell +mkdir /software +tar -zxvf jdkXXX.tar.gz /software/ +``` + +(2)配置环境变量 + +```shell +export JAVA_HOME=/software/jdk1.8.0_121 +export PATH=$JAVA_HOME/bin:$PATH +export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar +``` + +### 7.2 Python部署 + +```shell +pip install requests 或 sudo yum install -y requests +``` + +### 7.3 MySQL-python部署 + +- CentOS + + ``` + sudo yum install -y MySQL-python + ``` + +- Ubuntu + + ``` + sudo apt-get install -y python-pip + sudo pip install MySQL-python + ``` + +### 7.4 数据库部署 + +此处以Centos/Fedora为例。 + +(1)切换到root + +```shell +sudo -s +``` + +(2)安装mysql + +```shell +yum install mysql* +#某些版本的linux,需要安装mariadb,mariadb是mysql的一个分支 +yum install mariadb* +``` + +(3)启动mysql + +```shell +service mysqld start +#若安装了mariadb,则使用下面的命令启动 +systemctl start mariadb.service +``` + +(4)初始化数据库用户 + +初次登录 + +```shell +mysql -u root +``` + +给root设置密码和授权远程访问 + +```sql +mysql > SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123456'); +mysql > GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION; +``` + +**安全温馨提示:** + +1. 例子中给出的数据库密码(123456)仅为样例,强烈建议设置成复杂密码 +2. 例子中的远程授权设置会使数据库在所有网络上都可以访问,请按具体的网络拓扑和权限控制情况,设置网络和权限帐号 + +授权test用户本地访问数据库 + +```sql +mysql > create user 'test'@'localhost' identified by '123456'; +``` + +(5)测试连接 + +另开一个ssh测试本地用户test是否可以登录数据库 + +```shell +mysql -utest -p123456 -h 127.0.0.1 -P 3306 +``` + +登陆成功后,执行以下sql语句,若出现错误,则用户授权不成功 + +```sql +mysql > show databases; +mysql > use test; +``` + +(6)创建数据库 + +登录数据库 + +```shell +mysql -utest -p123456 -h 127.0.0.1 -P 3306 +``` + +创建数据库 + +```sql +mysql > create database db_browser; +``` + +## 8、常见问题 + +### 8.1 数据库安装后登录报错 + +腾讯云centos mysql安装完成后,登录报错:Access denied for user 'root'@'localhost' + +① 编辑 /etc/my.cnf ,在[mysqld] 部分最后添加一行 + +``` +skip-grant-tables +``` + +② 保存后重启mysql + +```shell +service mysqld restart +``` + +③ 输入以下命令,回车后输入密码再回车登录mysql + +``` +mysql -uroot -p mysql +``` + +### 8.2 找不到MySQLdb + +``` +Traceback (most recent call last): +... +ImportError: No module named MySQLdb +``` + +答:MySQL-python安装请参看部署附录7.3 + +### 8.3 部署时编译包下载慢 + +``` +... +Connecting to github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.216.112.19|:443... connected. +HTTP request sent, awaiting response... 200 OK +Length: 22793550 (22M) [application/octet-stream] +Saving to: ‘webase-front.zip’ + + 0% [ ] 77,974 37.8KB/s +``` + +答:部署过程会下载工程编译包,可能会因为网络原因导致过慢。此时,可以先手动下载( [webase-web](https://github.com/mingzhenliu/sss/releases/download/111/webase-web.zip) 、[webase-node-mgr](https://github.com/mingzhenliu/sss/releases/download/111/webase-node-mgr.zip) 、[webase-front](https://github.com/mingzhenliu/sss/releases/download/111/webase-front.zip)),再上传至服务器webase-deploy目录,在部署过程中根据提示不再重新下载编译包。 + +### 8.4 部署时数据库访问报错 + +``` +... +checking database connection +Traceback (most recent call last): + File "/data/temp/webase-deploy/comm/mysql.py", line 21, in dbConnect + conn = mdb.connect(host=mysql_ip, port=mysql_port, user=mysql_user, passwd=mysql_password, charset='utf8') + File "/usr/lib64/python2.7/site-packages/MySQLdb/__init__.py", line 81, in Connect + return Connection(*args, **kwargs) + File "/usr/lib64/python2.7/site-packages/MySQLdb/connections.py", line 193, in __init__ + super(Connection, self).__init__(*args, **kwargs2) +OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: YES)") +``` + +答:确认数据库用户名和密码 From 28f70028ab1c8329b0e0376aee14ac89f676ab63 Mon Sep 17 00:00:00 2001 From: mingzhenliu Date: Fri, 14 Jun 2019 16:48:20 +0800 Subject: [PATCH 026/119] add deploy --- deploy/build_chain.sh | 1165 +++++++++++++++++++++++++++++++ deploy/comm/__init__.py | 0 deploy/comm/build.py | 376 ++++++++++ deploy/comm/check.py | 136 ++++ deploy/comm/log.py | 52 ++ deploy/comm/mysql.py | 48 ++ deploy/comm/nginx.conf | 60 ++ deploy/comm/utils.py | 182 +++++ deploy/common.properties | 22 + deploy/deploy.py | 77 ++ install.md => deploy/install.md | 0 deploy/nodeconf | 1 + 12 files changed, 2119 insertions(+) create mode 100644 deploy/build_chain.sh create mode 100644 deploy/comm/__init__.py create mode 100644 deploy/comm/build.py create mode 100644 deploy/comm/check.py create mode 100644 deploy/comm/log.py create mode 100644 deploy/comm/mysql.py create mode 100644 deploy/comm/nginx.conf create mode 100644 deploy/comm/utils.py create mode 100644 deploy/common.properties create mode 100644 deploy/deploy.py rename install.md => deploy/install.md (100%) create mode 100644 deploy/nodeconf diff --git a/deploy/build_chain.sh b/deploy/build_chain.sh new file mode 100644 index 0000000..eb51ae3 --- /dev/null +++ b/deploy/build_chain.sh @@ -0,0 +1,1165 @@ +#!/bin/bash + +set -e + +ca_file= #CA key +node_num=1 +ip_file= +agency_array= +group_array= +ip_param= +use_ip_param= +ip_array= +output_dir=nodes +port_start=(30300 20200 8545) +state_type=storage +storage_type=LevelDB +conf_path="conf" +bin_path= +make_tar= +debug_log="false" +log_level="info" +logfile=build.log +listen_ip="127.0.0.1" +bcos_bin_name=fisco-bcos +guomi_mode= +docker_mode= +gm_conf_path="gmconf/" +current_dir=$(pwd) +consensus_type="pbft" +TASSL_CMD="${HOME}"/.tassl +auto_flush="true" +# trans timestamp from seconds to milliseconds +timestamp=$(($(date '+%s')*1000)) +chain_id=1 +fisco_version="" +OS= + +help() { + echo $1 + cat << EOF +Usage: + -l [Required] "ip1:nodeNum1,ip2:nodeNum2" e.g:"192.168.0.1:2,192.168.0.2:3" + -f [Optional] split by line, every line should be "ip:nodeNum agencyName groupList". eg "127.0.0.1:4 agency1 1,2" + -e Default download fisco-bcos from GitHub. If set -e, use the binary at the specified location + -o Default ./nodes/ + -p Default 30300,20200,8545 means p2p_port start from 30300, channel_port from 20200, jsonrpc_port from 8545 + -i Default 127.0.0.1. If set -i, listen 0.0.0.0 + -v Default get version from FISCO-BCOS/blob/master/release_note.txt. eg. 2.0.0-rc1 + -d Default off. If set -d, build with docker + -s Default storage. if set -s, use mpt + -S Default leveldb. if set -S, use external + -c Default PBFT. If set -c, use Raft + -C Default 1. Can set uint. + -g Default no + -z Default no + -t Default auto generate + -T Default off. If set -T, enable debug log + -F Default on. If set -F, disable log auto flush + -h Help +e.g + $0 -l "127.0.0.1:4" +EOF + +exit 0 +} + +LOG_WARN() +{ + local content=${1} + echo -e "\033[31m[WARN] ${content}\033[0m" +} + +LOG_INFO() +{ + local content=${1} + echo -e "\033[32m[INFO] ${content}\033[0m" +} + +parse_params() +{ +while getopts "f:l:o:p:e:t:v:icszhgTFdC:S" option;do + case $option in + f) ip_file=$OPTARG + use_ip_param="false" + ;; + l) ip_param=$OPTARG + use_ip_param="true" + ;; + o) output_dir=$OPTARG;; + i) listen_ip="0.0.0.0";; + v) fisco_version="$OPTARG";; + p) port_start=(${OPTARG//,/ }) + if [ ${#port_start[@]} -ne 3 ];then LOG_WARN "start port error. e.g: 30300,20200,8545" && exit 1;fi + ;; + e) bin_path=$OPTARG;; + s) state_type=mpt;; + S) storage_type="external";; + t) CertConfig=$OPTARG;; + c) consensus_type="raft";; + C) chain_id=$OPTARG + if [ -z $(grep '^[[:digit:]]*$' <<< "${chain_id}") ];then + LOG_WARN "${chain_id} is not a positive integer." + exit 1; + fi + ;; + T) debug_log="true" + log_level="debug" + ;; + F) auto_flush="false";; + z) make_tar="yes";; + g) guomi_mode="yes";; + d) docker_mode="yes";; + h) help;; + esac +done +} + +print_result() +{ +echo "================================================================" +LOG_INFO "Execute the following command to get FISCO-BCOS console" +echo " bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/download_console.sh)" +echo "================================================================" +[ -z ${docker_mode} ] && LOG_INFO "FISCO-BCOS Path : $bin_path" +[ ! -z ${docker_mode} ] && LOG_INFO "Docker tag : latest" +[ ! -z $ip_file ] && LOG_INFO "IP List File : $ip_file" +# [ ! -z $ip_file ] && LOG_INFO -e "Agencies/groups : ${#agency_array[@]}/${#groups[@]}" +LOG_INFO "Start Port : ${port_start[*]}" +LOG_INFO "Server IP : ${ip_array[*]}" +LOG_INFO "State Type : ${state_type}" +LOG_INFO "RPC listen IP : ${listen_ip}" +[ ! -z ${pkcs12_passwd} ] && LOG_INFO "SDK PKCS12 Passwd : ${pkcs12_passwd}" +LOG_INFO "Output Dir : ${output_dir}" +LOG_INFO "CA Key Path : $ca_file" +[ ! -z $guomi_mode ] && LOG_INFO "Guomi mode : $guomi_mode" +echo "================================================================" +LOG_INFO "All completed. Files in ${output_dir}" +} + +fail_message() +{ + echo $1 + false +} + +EXIT_CODE=-1 + +check_env() { + [ ! -z "$(openssl version | grep 1.0.2)" ] || [ ! -z "$(openssl version | grep 1.1)" ] || [ ! -z "$(openssl version | grep reSSL)" ] || { + echo "please install openssl!" + #echo "download openssl from https://www.openssl.org." + echo "use \"openssl version\" command to check." + exit $EXIT_CODE + } + if [ ! -z "$(openssl version | grep reSSL)" ];then + export PATH="/usr/local/opt/openssl/bin:$PATH" + fi + if [ "$(uname)" == "Darwin" ];then + OS="macOS" + elif [ "$(uname -s)" == " Linux " ];then + OS="Linux" + fi + +} + +# TASSL env +check_and_install_tassl() +{ + if [ ! -f "${HOME}/.tassl" ];then + curl -LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/tassl.tar.gz + LOG_INFO "Downloading tassl binary ..." + tar zxvf tassl.tar.gz + chmod u+x tassl + mv tassl ${HOME}/.tassl + fi +} + +getname() { + local name="$1" + if [ -z "$name" ]; then + return 0 + fi + [[ "$name" =~ ^.*/$ ]] && { + name="${name%/*}" + } + name="${name##*/}" + echo "$name" +} + +check_name() { + local name="$1" + local value="$2" + [[ "$value" =~ ^[a-zA-Z0-9._-]+$ ]] || { + echo "$name name [$value] invalid, it should match regex: ^[a-zA-Z0-9._-]+\$" + exit $EXIT_CODE + } +} + +file_must_exists() { + if [ ! -f "$1" ]; then + echo "$1 file does not exist, please check!" + exit $EXIT_CODE + fi +} + +dir_must_exists() { + if [ ! -d "$1" ]; then + echo "$1 DIR does not exist, please check!" + exit $EXIT_CODE + fi +} + +dir_must_not_exists() { + if [ -e "$1" ]; then + echo "$1 DIR exists, please clean old DIR!" + exit $EXIT_CODE + fi +} + +gen_chain_cert() { + path="$2" + name=$(getname "$path") + echo "$path --- $name" + dir_must_not_exists "$path" + check_name chain "$name" + + chaindir=$path + mkdir -p $chaindir + openssl genrsa -out $chaindir/ca.key 2048 + openssl req -new -x509 -days 3650 -subj "/CN=$name/O=fisco-bcos/OU=chain" -key $chaindir/ca.key -out $chaindir/ca.crt + mv cert.cnf $chaindir +} + +gen_agency_cert() { + chain="$2" + agencypath="$3" + name=$(getname "$agencypath") + + dir_must_exists "$chain" + file_must_exists "$chain/ca.key" + check_name agency "$name" + agencydir=$agencypath + dir_must_not_exists "$agencydir" + mkdir -p $agencydir + + openssl genrsa -out $agencydir/agency.key 2048 + openssl req -new -sha256 -subj "/CN=$name/O=fisco-bcos/OU=agency" -key $agencydir/agency.key -config $chain/cert.cnf -out $agencydir/agency.csr + openssl x509 -req -days 3650 -sha256 -CA $chain/ca.crt -CAkey $chain/ca.key -CAcreateserial\ + -in $agencydir/agency.csr -out $agencydir/agency.crt -extensions v4_req -extfile $chain/cert.cnf + + cp $chain/ca.crt $chain/cert.cnf $agencydir/ + cp $chain/ca.crt $agencydir/ca-agency.crt + more $agencydir/agency.crt | cat >>$agencydir/ca-agency.crt + rm -f $agencydir/agency.csr + + echo "build $name agency cert successful!" +} + +gen_cert_secp256k1() { + capath="$1" + certpath="$2" + name="$3" + type="$4" + openssl ecparam -out $certpath/${type}.param -name secp256k1 + openssl genpkey -paramfile $certpath/${type}.param -out $certpath/${type}.key + openssl pkey -in $certpath/${type}.key -pubout -out $certpath/${type}.pubkey + openssl req -new -sha256 -subj "/CN=${name}/O=fisco-bcos/OU=${type}" -key $certpath/${type}.key -config $capath/cert.cnf -out $certpath/${type}.csr + openssl x509 -req -days 3650 -sha256 -in $certpath/${type}.csr -CAkey $capath/agency.key -CA $capath/agency.crt\ + -force_pubkey $certpath/${type}.pubkey -out $certpath/${type}.crt -CAcreateserial -extensions v3_req -extfile $capath/cert.cnf + openssl ec -in $certpath/${type}.key -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32 | cat >$certpath/${type}.private + rm -f $certpath/${type}.csr +} + +gen_node_cert() { + if [ "" == "$(openssl ecparam -list_curves 2>&1 | grep secp256k1)" ]; then + echo "openssl don't support secp256k1, please upgrade openssl!" + exit $EXIT_CODE + fi + + agpath="$2" + agency=$(getname "$agpath") + ndpath="$3" + node=$(getname "$ndpath") + dir_must_exists "$agpath" + file_must_exists "$agpath/agency.key" + check_name agency "$agency" + dir_must_not_exists "$ndpath" + check_name node "$node" + + mkdir -p $ndpath + + gen_cert_secp256k1 "$agpath" "$ndpath" "$node" node + #nodeid is pubkey + openssl ec -in $ndpath/node.key -text -noout | sed -n '7,11p' | tr -d ": \n" | awk '{print substr($0,3);}' | cat >$ndpath/node.nodeid + # openssl x509 -serial -noout -in $ndpath/node.crt | awk -F= '{print $2}' | cat >$ndpath/node.serial + cp $agpath/ca.crt $agpath/agency.crt $ndpath + + cd $ndpath + + echo "build $node node cert successful!" +} + +generate_gmsm2_param() +{ + local output=$1 + cat << EOF > ${output} +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- + +EOF +} + +gen_chain_cert_gm() { + path="$2" + name=$(getname "$path") + echo "$path --- $name" + dir_must_not_exists "$path" + check_name chain "$name" + + chaindir=$path + mkdir -p $chaindir + + generate_gmsm2_param "gmsm2.param" + $TASSL_CMD genpkey -paramfile gmsm2.param -out $chaindir/gmca.key + $TASSL_CMD req -config gmcert.cnf -x509 -days 3650 -subj "/CN=$name/O=fiscobcos/OU=chain" -key $chaindir/gmca.key -extensions v3_ca -out $chaindir/gmca.crt + + ls $chaindir + + cp gmcert.cnf gmsm2.param $chaindir + + if $(cp gmcert.cnf gmsm2.param $chaindir) + then + echo "build chain ca succussful!" + else + echo "please input at least Common Name!" + fi +} + +gen_agency_cert_gm() { + chain="$2" + agencypath="$3" + name=$(getname "$agencypath") + + dir_must_exists "$chain" + file_must_exists "$chain/gmca.key" + check_name agency "$name" + agencydir=$agencypath + dir_must_not_exists "$agencydir" + mkdir -p $agencydir + + $TASSL_CMD genpkey -paramfile $chain/gmsm2.param -out $agencydir/gmagency.key + $TASSL_CMD req -new -subj "/CN=$name/O=fiscobcos/OU=agency" -key $agencydir/gmagency.key -config $chain/gmcert.cnf -out $agencydir/gmagency.csr + $TASSL_CMD x509 -req -CA $chain/gmca.crt -CAkey $chain/gmca.key -days 3650 -CAcreateserial -in $agencydir/gmagency.csr -out $agencydir/gmagency.crt -extfile $chain/gmcert.cnf -extensions v3_agency_root + + cp $chain/gmca.crt $chain/gmcert.cnf $chain/gmsm2.param $agencydir/ + cp $chain/gmca.crt $agencydir/ca-agency.crt + more $agencydir/gmagency.crt | cat >>$agencydir/ca-agency.crt + rm -f $agencydir/gmagency.csr + + echo "build $name agency cert successful!" +} + +gen_node_cert_with_extensions_gm() { + capath="$1" + certpath="$2" + name="$3" + type="$4" + extensions="$5" + + $TASSL_CMD genpkey -paramfile $capath/gmsm2.param -out $certpath/gm${type}.key + $TASSL_CMD req -new -subj "/CN=$name/O=fiscobcos/OU=agency" -key $certpath/gm${type}.key -config $capath/gmcert.cnf -out $certpath/gm${type}.csr + $TASSL_CMD x509 -req -CA $capath/gmagency.crt -CAkey $capath/gmagency.key -days 3650 -CAcreateserial -in $certpath/gm${type}.csr -out $certpath/gm${type}.crt -extfile $capath/gmcert.cnf -extensions $extensions + + rm -f $certpath/gm${type}.csr +} + +gen_node_cert_gm() { + if [ "" = "$(openssl ecparam -list_curves 2>&1 | grep secp256k1)" ]; then + echo "openssl don't support secp256k1, please upgrade openssl!" + exit $EXIT_CODE + fi + + agpath="$2" + agency=$(getname "$agpath") + ndpath="$3" + node=$(getname "$ndpath") + dir_must_exists "$agpath" + file_must_exists "$agpath/gmagency.key" + check_name agency "$agency" + + mkdir -p $ndpath + dir_must_exists "$ndpath" + check_name node "$node" + + mkdir -p $ndpath + gen_node_cert_with_extensions_gm "$agpath" "$ndpath" "$node" node v3_req + gen_node_cert_with_extensions_gm "$agpath" "$ndpath" "$node" ennode v3enc_req + #nodeid is pubkey + $TASSL_CMD ec -in $ndpath/gmnode.key -text -noout | sed -n '7,11p' | sed 's/://g' | tr "\n" " " | sed 's/ //g' | awk '{print substr($0,3);}' | cat > $ndpath/gmnode.nodeid + + #serial + if [ "" != "$($TASSL_CMD version | grep 1.0.2)" ]; + then + $TASSL_CMD x509 -text -in $ndpath/gmnode.crt | sed -n '5p' | sed 's/://g' | tr "\n" " " | sed 's/ //g' | sed 's/[a-z]/\u&/g' | cat > $ndpath/gmnode.serial + else + $TASSL_CMD x509 -text -in $ndpath/gmnode.crt | sed -n '4p' | sed 's/ //g' | sed 's/.*(0x//g' | sed 's/)//g' |sed 's/[a-z]/\u&/g' | cat > $ndpath/gmnode.serial + fi + + + cp $agpath/gmca.crt $agpath/gmagency.crt $ndpath + + cd $ndpath + + echo "build $node node cert successful!" +} + +generate_config_ini() +{ + local output=${1} + local ip=${2} + local offset=${ip_node_counts[${ip//./}]} + local node_groups=(${3//,/ }) + local prefix="" + if [ -n "$guomi_mode" ]; then + prefix="gm" + fi + cat << EOF > ${output} +[rpc] + ; rpc listen ip + listen_ip=${listen_ip} + ; channelserver listen port + channel_listen_port=$(( offset + port_start[1] )) + ; jsonrpc listen port + jsonrpc_listen_port=$(( offset + port_start[2] )) +[p2p] + ; p2p listen ip + listen_ip=0.0.0.0 + ; p2p listen port + listen_port=$(( offset + port_start[0] )) + ; nodes to connect + $ip_list + ;enable/disable network compress + ;enable_compress=false + +;certificate rejected list +[certificate_blacklist] + ; crl.0 should be nodeid, nodeid's length is 128 + ;crl.0= + +;group configurations +;WARNING: group 0 is forbided +[group] + group_data_path=data/ + group_config_path=${conf_path}/ + +;certificate configuration +[network_security] + ; directory the certificates located in + data_path=${conf_path}/ + ; the node private key file + key=${prefix}node.key + ; the node certificate file + cert=${prefix}node.crt + ; the ca certificate file + ca_cert=${prefix}ca.crt + +; storage security releated configurations +[storage_security] +; enable storage_security or not +;enable=true +; the IP of key mananger +;key_manager_ip= +; the Port of key manager +;key_manager_port= +;cipher_data_key= + +[chain] + id=${chain_id} +[compatibility] + supported_version=${fisco_version} +;log configurations +[log] + ; the directory of the log + log_path=./log + ; info debug trace + level=${log_level} + ; MB + max_log_file_size=200 + ; control log auto_flush + flush=${auto_flush} + ; easylog config + format=%level|%datetime{%Y-%M-%d %H:%m:%s:%g}|%msg + log_flush_threshold=100 +EOF +} + +generate_group_genesis() +{ + local output=$1 + local index=$2 + local node_list=$3 + cat << EOF > ${output} +;consensus configuration +[consensus] + ;consensus algorithm type, now support PBFT(consensus_type=pbft) and Raft(consensus_type=raft) + consensus_type=${consensus_type} + ;the max number of transactions of a block + max_trans_num=1000 + ;the node id of leaders + ${node_list} +[storage] + ;storage db type, leveldb or external + type=${storage_type} + topic=DB +[state] + ;support mpt/storage + type=${state_type} + +;tx gas limit +[tx] + gas_limit=300000000 +[group] + id=${index} + timestamp=${timestamp} +EOF +} + +function generate_group_ini() +{ + local output="${1}" + cat << EOF > ${output} +; the ttl for broadcasting pbft message +[consensus] + ;ttl=2 + ;min block generation time(ms), the max block generation time is 1000 ms + ;min_block_generation_time=500 + ;enable_dynamic_block_size=true + +;txpool limit +[tx_pool] + limit=150000 +[tx_execute] + enable_parallel=true +EOF +} + +generate_cert_conf() +{ + local output=$1 + cat << EOF > ${output} +[ca] +default_ca=default_ca +[default_ca] +default_days = 365 +default_md = sha256 + +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +[req_distinguished_name] +countryName = CN +countryName_default = CN +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default =GuangDong +localityName = Locality Name (eg, city) +localityName_default = ShenZhen +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = fisco-bcos +commonName = Organizational commonName (eg, fisco-bcos) +commonName_default = fisco-bcos +commonName_max = 64 + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v4_req ] +basicConstraints = CA:TRUE + +EOF +} + +generate_script_template() +{ + local filepath=$1 + cat << EOF > "${filepath}" +#!/bin/bash +SHELL_FOLDER=\$(cd \$(dirname \$0);pwd) + +EOF + chmod +x ${filepath} +} + +generate_cert_conf_gm() +{ + local output=$1 + cat << EOF > ${output} +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] +tsa_policy1 = 1.2.3.4.1 +tsa_policy2 = 1.2.3.4.5.6 +tsa_policy3 = 1.2.3.4.5.7 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_md = sm3 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +x509_extensions = v3_ca # The extentions to add to the self signed cert + +string_mask = utf8only + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = CN +countryName_default = CN +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default =GuangDong +localityName = Locality Name (eg, city) +localityName_default = ShenZhen +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = fisco +commonName = Organizational commonName (eg, fisco) +commonName_default = fisco +commonName_max = 64 + +[ usr_cert ] +basicConstraints=CA:FALSE +nsComment = "OpenSSL Generated Certificate" + +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature + +[ v3enc_req ] + +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = keyAgreement, keyEncipherment, dataEncipherment + +[ v3_agency_root ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = CA:true +keyUsage = cRLSign, keyCertSign + +[ v3_ca ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = CA:true +keyUsage = cRLSign, keyCertSign + +EOF +} + +generate_node_scripts() +{ + local output=$1 + local docker_tag="latest" + generate_script_template "$output/start.sh" + local ps_cmd="\`ps aux|grep \${fisco_bcos}|grep -v grep|awk '{print \$2}'\`" + local start_cmd="nohup \${fisco_bcos} -c config.ini 2>>nohup.out" + local stop_cmd="kill \${node_pid}" + local pid="pid" + local log_cmd="cat nohup.out" + if [ ! -z ${docker_mode} ];then + ps_cmd="\`docker ps |grep \${SHELL_FOLDER//\//} | grep -v grep|awk '{print \$1}'\`" + start_cmd="docker run -d --rm --name \${SHELL_FOLDER//\//} -v \${SHELL_FOLDER}:/data --network=host -w=/data fiscoorg/fiscobcos:${docker_tag} -c config.ini >>nohup.out" + stop_cmd="docker kill \${node_pid} 2>/dev/null" + pid="container id" + log_cmd="docker logs \${SHELL_FOLDER//\//}" + fi + cat << EOF >> "$output/start.sh" +fisco_bcos=\${SHELL_FOLDER}/../${bcos_bin_name} +cd \${SHELL_FOLDER} +node=\$(basename \${SHELL_FOLDER}) +node_pid=${ps_cmd} +if [ ! -z \${node_pid} ];then + echo " \${node} is running, ${pid} is \$node_pid." + exit 0 +else + ${start_cmd} & + sleep 1.5 +fi +try_times=4 +i=0 +while [ \$i -lt \${try_times} ] +do + node_pid=${ps_cmd} + if [ ! -z \${node_pid} ];then + echo -e "\033[32m \${node} start successfully\033[0m" + exit 0 + fi + sleep 0.5 + ((i=i+1)) +done +echo -e "\033[31m Exceed waiting time. Please try again to start \${node} \033[0m" +${log_cmd} +exit 1 +EOF + generate_script_template "$output/stop.sh" + cat << EOF >> "$output/stop.sh" +fisco_bcos=\${SHELL_FOLDER}/../${bcos_bin_name} +node=\$(basename \${SHELL_FOLDER}) +node_pid=${ps_cmd} +try_times=5 +i=0 +while [ \$i -lt \${try_times} ] +do + if [ -z \${node_pid} ];then + echo " \${node} isn't running." + exit 0 + fi + [ ! -z \${node_pid} ] && ${stop_cmd} > /dev/null + sleep 0.6 + node_pid=${ps_cmd} + if [ -z \${node_pid} ];then + echo -e "\033[32m stop \${node} success.\033[0m" + exit 0 + fi + ((i=i+1)) +done +echo " Exceed maximum number of retries. Please try again to stop \${node}" +exit 1 +EOF +} + + +genTransTest() +{ + local output=$1 + local file="${output}/.transTest.sh" + generate_script_template "${file}" + cat << EOF > "${file}" +# This script only support for block number smaller than 65535 - 256 + +ip_port=http://127.0.0.1:$(( port_start[2] )) +trans_num=1 +target_group=1 +version= +if [ \$# -ge 1 ];then + trans_num=\$1 +fi +if [ \$# -ge 2 ];then + target_group=\$2 +fi + +getNodeVersion() +{ + result="\$(curl -X POST --data '{"jsonrpc":"2.0","method":"getClientVersion","params":[],"id":1}' \${ip_port})" + version="\$(echo \${result} | cut -c250- | cut -d \" -f3)" +} + +block_limit() +{ + result=\$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"getBlockNumber","params":['\${target_group}'],"id":83}' \${ip_port}) + if [ \$(echo \${result} | grep -i failed | wc -l) -gt 0 ] || [ -z \${result} ];then + echo "getBlockNumber error!" + exit 1 + fi + blockNumber=\$(echo \${result}| cut -d \" -f 10) + printf "%04x" \$((\$blockNumber+0x100)) +} + +send_a_tx() +{ + limit=\$(block_limit) + random_id="\$(date +%s)\$(printf "%09d" \${RANDOM})" + if [ \${#limit} -gt 4 ];then echo "blockLimit exceed 0xffff, this scripts is unavailable!"; exit 0;fi + if [ "\${version}" == "2.0.0-rc1" ];then + txBytes="f8f0a02ade583745343a8f9a70b40db996fbe69c63531832858\${random_id}85174876e7ff8609184e729fff82\${limit}94d6f1a71052366dbae2f7ab2d5d5845e77965cf0d80b86448f85bce000000000000000000000000000000000000000000000000000000000000001bf5bd8a9e7ba8b936ea704292ff4aaa5797bf671fdc8526dcd159f23c1f5a05f44e9fa862834dc7cb4541558f2b4961dc39eaaf0af7f7395028658d0e01b86a371ca0e33891be86f781ebacdafd543b9f4f98243f7b52d52bac9efa24b89e257a354da07ff477eb0ba5c519293112f1704de86bd2938369fbf0db2dff3b4d9723b9a87d" + else + txBytes="f8eca003eb675ec791c2d19858c91d0046821c27d815e2e9c15\${random_id}0a8402faf08082\${limit}948c17cf316c1063ab6c89df875e96c9f0f5b2f74480b8644ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a464953434f2042434f53000000000000000000000000000000000000000000000101801ba09edf7c0cb63645442aff11323916d51ec5440de979950747c0189f338afdcefda02f3473184513c6a3516e066ea98b7cfb55a79481c9db98e658dd016c37f03dcf" + fi + #echo \$txBytes + curl -s -X POST --data '{"jsonrpc":"2.0","method":"sendRawTransaction","params":['\${target_group}', "'\$txBytes'"],"id":83}' \${ip_port} +} + +send_many_tx() +{ + for j in \$(seq 1 \$1) + do + echo 'Send transaction: ' \$j + send_a_tx \${ip_port} + done +} +getNodeVersion +echo "Use version:\${version}" +send_many_tx \${trans_num} + +EOF +} + +generate_server_scripts() +{ + local output=$1 + genTransTest "${output}" + generate_script_template "$output/start_all.sh" + # echo "ip_array=(\$(ifconfig | grep inet | grep -v inet6 | awk '{print \$2}'))" >> "$output/start_all.sh" + # echo "if echo \${ip_array[@]} | grep -w \"${ip}\" &>/dev/null; then echo \"start node_${ip}_${i}\" && bash \${SHELL_FOLDER}/node_${ip}_${i}/start.sh; fi" >> "${output_dir}/start_all.sh" + cat << EOF >> "$output/start_all.sh" +for directory in \`ls \${SHELL_FOLDER}\` +do + if [[ -d "\${SHELL_FOLDER}/\${directory}" && -f "\${SHELL_FOLDER}/\${directory}/start.sh" ]];then + echo "try to start \${directory}" + bash \${SHELL_FOLDER}/\${directory}/start.sh & + fi +done +sleep 3.5 +EOF + generate_script_template "$output/stop_all.sh" + cat << EOF >> "$output/stop_all.sh" +for directory in \`ls \${SHELL_FOLDER}\` +do + if [[ -d "\${SHELL_FOLDER}/\${directory}" && -f "\${SHELL_FOLDER}/\${directory}/stop.sh" ]];then + echo "try to stop \${directory}" + bash \${SHELL_FOLDER}/\${directory}/stop.sh & + fi +done +sleep 3 +EOF +} + +parse_ip_config() +{ + local config=$1 + n=0 + while read line;do + ip_array[n]=$(echo ${line} | awk '{print $1}') + agency_array[n]=$(echo ${line} | awk '{print $2}') + group_array[n]=$(echo ${line} | awk '{print $3}') + if [ -z "${ip_array[$n]}" -o -z "${agency_array[$n]}" -o -z "${group_array[$n]}" ];then + LOG_WARN "Please check ${config}, make sure there is no empty line!" + return 1 + fi + ((++n)) + done < ${config} +} + +main() +{ +output_dir="$(pwd)/${output_dir}" +[ -z $use_ip_param ] && help 'ERROR: Please set -l or -f option.' +if [ "${use_ip_param}" == "true" ];then + ip_array=(${ip_param//,/ }) +elif [ "${use_ip_param}" == "false" ];then + if ! parse_ip_config $ip_file ;then + echo "Parse $ip_file error!" + exit 1 + fi +else + help +fi + + +dir_must_not_exists ${output_dir} +mkdir -p "${output_dir}" + +# get fisco_version +if [ -z "${fisco_version}" ];then + fisco_version=$(curl -s https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/release_note.txt | sed "s/^[vV]//") +fi + +# download fisco-bcos and check it +if [ -z ${docker_mode} ];then + if [[ -z ${bin_path} && -z ${OS} ]];then + bin_path=${output_dir}/${bcos_bin_name} + package_name="fisco-bcos.tar.gz" + [ ! -z "$guomi_mode" ] && package_name="fisco-bcos-gm.tar.gz" + Download_Link="https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v${fisco_version}/${package_name}" + LOG_INFO "Downloading fisco-bcos binary from ${Download_Link} ..." + curl -LO ${Download_Link} + tar -zxf ${package_name} && mv fisco-bcos ${bin_path} && rm ${package_name} + chmod a+x ${bin_path} + elif [[ -z ${bin_path} && ! -z ${OS} ]];then + echo "Please use docker mode to run fisco-bcos on macOS Or compile source code and use -e option to specific fisco-bcos binary path" + exit 1 + else + echo "Checking fisco-bcos binary..." + bin_version=$(${bin_path} -v) + if [ -z "$(echo ${bin_version} | grep 'FISCO-BCOS')" ];then + LOG_WARN "${bin_path} is wrong. Please correct it and try again." + exit 1 + fi + if [[ ! -z ${guomi_mode} && -z $(echo ${bin_version} | grep 'gm') ]];then + LOG_WARN "${bin_path} isn't gm version. Please correct it and try again." + exit 1 + fi + if [[ -z ${guomi_mode} && ! -z $(echo ${bin_version} | grep 'gm') ]];then + LOG_WARN "${bin_path} isn't standard version. Please correct it and try again." + exit 1 + fi + echo "Binary check passed." + fi +fi +if [ -z ${CertConfig} ] || [ ! -e ${CertConfig} ];then + # CertConfig="${output_dir}/cert.cnf" + generate_cert_conf "cert.cnf" +else + cp ${CertConfig} . +fi + +if [ "${use_ip_param}" == "true" ];then + for i in $(seq 0 ${#ip_array[*]});do + agency_array[i]="agency" + group_array[i]=1 + done +fi + +# prepare CA +echo "==============================================================" +if [ ! -e "$ca_file" ]; then + echo "Generating CA key..." + dir_must_not_exists ${output_dir}/chain + gen_chain_cert "" ${output_dir}/chain >${output_dir}/${logfile} 2>&1 || fail_message "openssl error!" + mv ${output_dir}/chain ${output_dir}/cert + if [ "${use_ip_param}" == "false" ];then + for agency_name in ${agency_array[*]};do + if [ ! -d ${output_dir}/cert/${agency_name} ];then + gen_agency_cert "" ${output_dir}/cert ${output_dir}/cert/${agency_name} >${output_dir}/${logfile} 2>&1 + fi + done + else + gen_agency_cert "" ${output_dir}/cert ${output_dir}/cert/agency >${output_dir}/${logfile} 2>&1 + fi + ca_file="${output_dir}/cert/ca.key" +fi + +if [ -n "$guomi_mode" ]; then + check_and_install_tassl + + generate_cert_conf_gm "gmcert.cnf" + + echo "Generating Guomi CA key..." + dir_must_not_exists ${output_dir}/gmchain + gen_chain_cert_gm "" ${output_dir}/gmchain >${output_dir}/build.log 2>&1 || fail_message "openssl error!" #生成secp256k1算法的CA密钥 + mv ${output_dir}/gmchain ${output_dir}/gmcert + gen_agency_cert_gm "" ${output_dir}/gmcert ${output_dir}/gmcert/agency >${output_dir}/build.log 2>&1 + ca_file="${output_dir}/gmcert/ca.key" +fi + + +echo "==============================================================" +echo "Generating keys ..." +nodeid_list="" +ip_list="" +count=0 +server_count=0 +groups= +ip_node_counts= +groups_count= +for line in ${ip_array[*]};do + ip=${line%:*} + num=${line#*:} + checkIP=$(echo $ip|grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") + if [ -z "${checkIP}" ];then + LOG_WARN "Please check IP address: ${ip}" + fi + [ "$num" == "$ip" ] || [ -z "${num}" ] && num=${node_num} + echo "Processing IP:${ip} Total:${num} Agency:${agency_array[${server_count}]} Groups:${group_array[server_count]}" + [ -z "${ip_node_counts[${ip//./}]}" ] && ip_node_counts[${ip//./}]=0 + for ((i=0;i> ${output_dir}/${logfile} + node_dir="${output_dir}/${ip}/node${ip_node_counts[${ip//./}]}" + [ -d "${node_dir}" ] && echo "${node_dir} exist! Please delete!" && exit 1 + + while : + do + gen_node_cert "" ${output_dir}/cert/${agency_array[${server_count}]} ${node_dir} >${output_dir}/${logfile} 2>&1 + mkdir -p ${conf_path}/ + rm node.param node.private node.pubkey agency.crt + mv *.* ${conf_path}/ + + #private key should not start with 00 + cd ${output_dir} + privateKey=$(openssl ec -in "${node_dir}/${conf_path}/node.key" -text 2> /dev/null| sed -n '3,5p' | sed 's/://g'| tr "\n" " "|sed 's/ //g') + len=${#privateKey} + head2=${privateKey:0:2} + if [ "64" != "${len}" ] || [ "00" == "$head2" ];then + rm -rf ${node_dir} + continue; + fi + + if [ -n "$guomi_mode" ]; then + gen_node_cert_gm "" ${output_dir}/gmcert/agency ${node_dir} >${output_dir}/build.log 2>&1 + mkdir -p ${gm_conf_path}/ + mv ./*.* ${gm_conf_path}/ + + #private key should not start with 00 + cd ${output_dir} + privateKey=$($TASSL_CMD ec -in "${node_dir}/${gm_conf_path}/gmnode.key" -text 2> /dev/null| sed -n '3,5p' | sed 's/://g'| tr "\n" " "|sed 's/ //g') + len=${#privateKey} + head2=${privateKey:0:2} + if [ "64" != "${len}" ] || [ "00" == "$head2" ];then + rm -rf ${node_dir} + continue; + fi + fi + break; + done + cat ${output_dir}/cert/${agency_array[${server_count}]}/agency.crt >> ${node_dir}/${conf_path}/node.crt + + if [ -n "$guomi_mode" ]; then + cat ${output_dir}/gmcert/agency/gmagency.crt >> ${node_dir}/${gm_conf_path}/gmnode.crt + cat ${output_dir}/gmcert/gmca.crt >> ${node_dir}/${gm_conf_path}/gmnode.crt + + #move origin conf to gm conf + rm ${node_dir}/${conf_path}/node.nodeid + cp ${node_dir}/${conf_path} ${node_dir}/${gm_conf_path}/origin_cert -r + fi + + if [ -n "$guomi_mode" ]; then + nodeid=$($TASSL_CMD ec -in "${node_dir}/${gm_conf_path}/gmnode.key" -text 2> /dev/null | perl -ne '$. > 6 and $. < 12 and ~s/[\n:\s]//g and print' | perl -ne 'print substr($_, 2)."\n"') + else + nodeid=$(openssl ec -in "${node_dir}/${conf_path}/node.key" -text 2> /dev/null | perl -ne '$. > 6 and $. < 12 and ~s/[\n:\s]//g and print' | perl -ne 'print substr($_, 2)."\n"') + fi + + if [ -n "$guomi_mode" ]; then + #remove original cert files + rm ${node_dir:?}/${conf_path} -rf + mv ${node_dir}/${gm_conf_path} ${node_dir}/${conf_path} + fi + + + if [ "${use_ip_param}" == "false" ];then + node_groups=(${group_array[server_count]//,/ }) + for j in ${node_groups[@]};do + if [ -z "${groups_count[${j}]}" ];then groups_count[${j}]=0;fi + echo "groups_count[${j}]=${groups_count[${j}]}" >> ${output_dir}/${logfile} + groups[${j}]=$"${groups[${j}]}node.${groups_count[${j}]}=${nodeid} + " + ((++groups_count[j])) + done + else + nodeid_list=$"${nodeid_list}node.${count}=${nodeid} + " + fi + + ip_list=$"${ip_list}node.${count}="${ip}:$(( ${ip_node_counts[${ip//./}]} + port_start[0] ))" + " + ip_node_counts[${ip//./}]=$(( ${ip_node_counts[${ip//./}]} + 1 )) + ((++count)) + done + sdk_path="${output_dir}/${ip}/sdk" + if [ ! -d ${sdk_path} ];then + gen_node_cert "" ${output_dir}/cert/${agency_array[${server_count}]} "${sdk_path}">${output_dir}/${logfile} 2>&1 + cat ${output_dir}/cert/${agency_array[${server_count}]}/agency.crt >> node.crt + rm node.param node.private node.pubkey node.nodeid agency.crt + cp ${output_dir}/cert/ca.crt ${sdk_path}/ + cd ${output_dir} + fi + ((++server_count)) +done + +ip_node_counts=() +echo "==============================================================" +echo "Generating configurations..." +cd ${current_dir} +server_count=0 +for line in ${ip_array[*]};do + ip=${line%:*} + num=${line#*:} + [ "$num" == "$ip" ] || [ -z "${num}" ] && num=${node_num} + [ -z "${ip_node_counts[${ip//./}]}" ] && ip_node_counts[${ip//./}]=0 + echo "Processing IP:${ip} Total:${num} Agency:${agency_array[${server_count}]} Groups:${group_array[server_count]}" + for ((i=0;i> ${output_dir}/${logfile} + node_dir="${output_dir}/${ip}/node${ip_node_counts[${ip//./}]}" + generate_config_ini "${node_dir}/config.ini" ${ip} "${group_array[server_count]}" + if [ "${use_ip_param}" == "false" ];then + node_groups=(${group_array[${server_count}]//,/ }) + for j in ${node_groups[@]};do + generate_group_genesis "$node_dir/${conf_path}/group.${j}.genesis" "${j}" "${groups[${j}]}" + generate_group_ini "$node_dir/${conf_path}/group.${j}.ini" + done + else + generate_group_genesis "$node_dir/${conf_path}/group.1.genesis" "1" "${nodeid_list}" + generate_group_ini "$node_dir/${conf_path}/group.1.ini" + fi + generate_node_scripts "${node_dir}" + ip_node_counts[${ip//./}]=$(( ${ip_node_counts[${ip//./}]} + 1 )) + done + generate_server_scripts "${output_dir}/${ip}" + if [ -z ${docker_mode} ];then cp "$bin_path" "${output_dir}/${ip}/fisco-bcos"; fi + if [ -n "$make_tar" ];then cd ${output_dir} && tar zcf "${ip}.tar.gz" "${ip}" && cd ${current_dir};fi + ((++server_count)) +done +rm ${output_dir}/${logfile} +if [ "${use_ip_param}" == "false" ];then +echo "==============================================================" + for l in $(seq 0 ${#groups_count[@]});do + if [ ! -z "${groups_count[${l}]}" ];then echo "Group:${l} has ${groups_count[${l}]} nodes";fi + done +fi + +} + +check_env +parse_params $@ +main +print_result diff --git a/deploy/comm/__init__.py b/deploy/comm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deploy/comm/build.py b/deploy/comm/build.py new file mode 100644 index 0000000..311198c --- /dev/null +++ b/deploy/comm/build.py @@ -0,0 +1,376 @@ +#!/usr/bin/python +# encoding: utf-8 + +import sys +import os +from utils import * +from mysql import * + +baseDir = getBaseDir() +currentDir = getCurrentBaseDir() + +def do(): + print "===================== deploy start... =====================" + startNode() + startWeb() + startMgr() + startFront() + print "===================== deploy end... =====================" + print "===================== version dev-0.8 =====================" + return + +def end(): + stopNode() + stopWeb() + stopMgr() + stopFront() + return + +def startNode(): + print "============== node start... ==============" + fisco_version = getCommProperties("fisco.version") + nodes = getCommProperties("node.counts") + node_p2pPort = int(getCommProperties("node.p2pPort")) + node_channelPort = int(getCommProperties("node.channelPort")) + node_rpcPort = int(getCommProperties("node.rpcPort")) + + # init configure file + if not os.path.exists(currentDir + "/nodetemp"): + doCmd('cp -f nodeconf nodetemp') + else: + doCmd('cp -f nodetemp nodeconf') + + node_counts = 2 + if nodes is not "nodeCounts": + node_counts = int(nodes) + doCmd('sed -i "s/nodeCounts/{}/g" nodeconf'.format(node_counts)) + + if not os.path.exists("{}/nodes".format(currentDir)): + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result_build = doCmd("./build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + if result_build["status"] == 0: + if_build = 'completed' in result_build["output"] + if not if_build: + print "======= node build fail! =======" + sys.exit(0) + else: + print "======= node build fail! =======" + sys.exit(0) + else: + info = raw_input("节点目录nodes已经存在。是否重新安装?[y/n]:") + if info == "y" or info == "Y": + doCmdIgnoreException("./nodes/127.0.0.1/stop_all.sh") + doCmd("rm -rf nodes") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result_build = doCmd("./build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + if result_build["status"] == 0: + if_build = 'completed' in result_build["output"] + if not if_build: + print "======= node build fail! =======" + sys.exit(0) + else: + print "======= node build fail! =======" + sys.exit(0) + + node_dir = currentDir + "/nodes/127.0.0.1" + os.chdir(node_dir) + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + os.system("./start_all.sh") + print "============== node end... ==============" + return + +def stopNode(): + if not os.path.exists("{}/nodes".format(currentDir)): + print "======= nodes is not exists! =======" + sys.exit(0) + + node_dir = currentDir + "/nodes/127.0.0.1" + os.chdir(node_dir) + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + os.system("./stop_all.sh") + return + +def changeWebConfig(): + # get properties + deploy_ip = "127.0.0.1" + web_port = getCommProperties("web.port") + mgr_port = getCommProperties("mgr.port") + + # init configure file + web_conf_dir = currentDir + "/comm" + if not os.path.exists(web_conf_dir + "/temp.conf"): + doCmd('cp -f {}/nginx.conf {}/temp.conf'.format(web_conf_dir, web_conf_dir)) + else: + doCmd('cp -f {}/temp.conf {}/nginx.conf'.format(web_conf_dir, web_conf_dir)) + + # change web config + web_dir = currentDir + "/webase-web" + web_log_dir = web_dir + "/log" + doCmd('mkdir -p {}'.format(web_log_dir)) + doCmd('sed -i "s/127.0.0.1/{}/g" {}/comm/nginx.conf'.format(deploy_ip, currentDir)) + doCmd('sed -i "s/3002/{}/g" {}/comm/nginx.conf'.format(web_port, currentDir)) + doCmd('sed -i "s/10.0.0.1:8083/{}:{}/g" {}/comm/nginx.conf'.format(deploy_ip, mgr_port, currentDir)) + doCmd('sed -i "s:log_path:{}:g" {}/comm/nginx.conf'.format(web_log_dir, currentDir)) + doCmd('sed -i "s:web_page_url:{}:g" {}/comm/nginx.conf'.format(web_dir, currentDir)) + + return + +def startWeb(): + print "============== web start... ==============" + os.chdir(currentDir) + pullSourceExtract("web.package.url","webase-web") + changeWebConfig() + + nginx_config_dir = currentDir + "/comm/nginx.conf" + res = doCmd("which nginx") + if res["status"] == 0: + res2 = doCmd("sudo " + res["output"] + " -c " + nginx_config_dir) + if res2["status"] == 0: + print "======= web start success! =======" + else: + print "======= web start fail! =======" + sys.exit(0) + else: + print "======= web start fail! =======" + sys.exit(0) + print "============== web end... ==============" + return + +def stopWeb(): + if os.path.exists("/run/nginx-webase-web.pid"): + fin = open('/run/nginx-webase-web.pid', 'r') + pid = fin.read() + cmd = "sudo kill -QUIT {}".format(pid) + os.system(cmd) + doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") + print "======= web stop success! =======" + else: + print "======= web is not running! =======" + return + +def changeMgrConfig(): + # get properties + mgr_port = getCommProperties("mgr.port") + mysql_ip = getCommProperties("mysql.ip") + mysql_port = getCommProperties("mysql.port") + mysql_user = getCommProperties("mysql.user") + mysql_password = getCommProperties("mysql.password") + mysql_database = getCommProperties("mysql.database") + + # init file + server_dir = currentDir + "/webase-node-mgr" + script_dir = server_dir + "/script" + conf_dir = server_dir + "/conf" + if not os.path.exists(script_dir + "/temp.sh"): + doCmd('cp -f {}/webase.sh {}/temp.sh'.format(script_dir, script_dir)) + else: + doCmd('cp -f {}/temp.sh {}/webase.sh'.format(script_dir, script_dir)) + if not os.path.exists(conf_dir + "/temp.yml"): + doCmd('cp -f {}/application.yml {}/temp.yml'.format(conf_dir, conf_dir)) + else: + doCmd('cp -f {}/temp.yml {}/application.yml'.format(conf_dir, conf_dir)) + + # change script config + doCmd('sed -i "s/defaultAccount/{}/g" {}/webase.sh'.format(mysql_user, script_dir)) + doCmd('sed -i "s/defaultPassword/{}/g" {}/webase.sh'.format(mysql_password, script_dir)) + doCmd('sed -i "s/fisco-bcos-data/{}/g" {}/webase.sh'.format(mysql_database, script_dir)) + + # change server config + doCmd('sed -i "s/8080/{}/g" {}/application.yml'.format(mgr_port, conf_dir)) + doCmd('sed -i "s/127.0.0.1/{}/g" {}/application.yml'.format(mysql_ip, conf_dir)) + doCmd('sed -i "s/3306/{}/g" {}/application.yml'.format(mysql_port, conf_dir)) + doCmd('sed -i "s/defaultAccount/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) + doCmd('sed -i "s/defaultPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) + doCmd('sed -i "s/fisco-bcos-data/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) + + return + +def startMgr(): + print "============== mgr start... ==============" + os.chdir(currentDir) + pullSourceExtract("mgr.package.url","webase-node-mgr") + changeMgrConfig() + dbConnect() + + mysql_ip = getCommProperties("mysql.ip") + mysql_port = getCommProperties("mysql.port") + server_dir = currentDir + "/webase-node-mgr" + script_dir = server_dir + "/script" + + info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + if info == "y" or info == "Y": + os.chdir(script_dir) + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + dbResult = doCmd('./webase.sh {} {}'.format(mysql_ip, mysql_port)) + if dbResult["status"] == 0: + if_success = 'success' in dbResult["output"] + if if_success: + print "======= script init success! =======" + else: + print "======= script init fail! =======" + print dbResult["output"] + sys.exit(0) + else: + print "======= script init fail! =======" + sys.exit(0) + + os.chdir(server_dir) + doCmdIgnoreException("source /etc/profile") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result = doCmd("./start.sh") + if result["status"] == 0: + if_started = 'started' in result["output"] + if if_started: + info = raw_input("mgr进程已经存在,是否kill进程强制安装?[y/n]:") + if info == "y" or info == "Y": + doCmd("./stop.sh") + result_start = doCmd("./start.sh") + if result_start["status"] == 0: + if_success = 'Success' in result_start["output"] + if if_success: + print "======= mgr start success! =======" + else: + print "======= mgr start fail! =======" + sys.exit(0) + else: + print "======= mgr start fail! =======" + sys.exit(0) + return + else: + sys.exit(0) + if_success = 'Success' in result["output"] + if if_success: + print "======= mgr start success! =======" + else: + print "======= mgr start fail! =======" + sys.exit(0) + else: + print "======= mgr start fail! =======" + sys.exit(0) + print "============== mgr end... ==============" + return + +def stopMgr(): + server_dir = currentDir + "/webase-node-mgr" + os.chdir(server_dir) + doCmdIgnoreException("source /etc/profile") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result = doCmd("./stop.sh") + if result["status"] == 0: + if_success = 'Success' in result["output"] + if if_success: + print "======= mgr stop success! =======" + else: + print "======= mgr is not running! =======" + else: + print "======= mgr stop fail! =======" + return + +def changeFrontConfig(): + # get properties + deploy_ip = "127.0.0.1" + mgr_port = getCommProperties("mgr.port") + frontPort = getCommProperties("front.port") + nodeChannelPort = getCommProperties("node.channelPort") + frontDb = getCommProperties("front.h2.db") + monitorDisk = getCommProperties("monitorDisk") + + # init file + server_dir = currentDir + "/webase-front/conf" + db_dir = currentDir + "/h2" + doCmdIgnoreException("mkdir -p {}".format(db_dir)) + if not os.path.exists(server_dir + "/temp.yml"): + doCmd('cp -f {}/application.yml {}/temp.yml'.format(server_dir, server_dir)) + else: + doCmd('cp -f {}/temp.yml {}/application.yml'.format(server_dir, server_dir)) + + # change server config + doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) + doCmd('sed -i "s/8081/{}/g" {}/application.yml'.format(frontPort, server_dir)) + doCmd('sed -i "s/10.0.0.1:8080/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) + doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) + doCmd('sed -i "s%front_db%{}%g" {}/application.yml'.format(frontDb, server_dir)) + doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(monitorDisk, server_dir)) + + return + +def startFront(): + print "============== front start... ==============" + os.chdir(currentDir) + pullSourceExtract("front.package.url","webase-front") + changeFrontConfig() + + # check front db + frontDb = getCommProperties("front.h2.db") + db_dir = currentDir+"/h2" + res_file = checkFileName(db_dir,frontDb) + if res_file: + info = raw_input("front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + if info == "y" or info == "Y": + doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) + + nodeDir = currentDir + "/nodes/127.0.0.1/sdk" + server_dir = currentDir + "/webase-front" + os.chdir(server_dir) + # copy crt + copyFiles(nodeDir, server_dir + "/conf") + + doCmdIgnoreException("source /etc/profile") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result = doCmd("./start.sh") + if result["status"] == 0: + if_started = 'started' in result["output"] + if if_started: + info = raw_input("front进程已经存在,是否kill进程强制安装?[y/n]:") + if info == "y" or info == "Y": + doCmd("./stop.sh") + result_start = doCmd("sh start.sh") + if result_start["status"] == 0: + if_success = 'Success' in result_start["output"] + if if_success: + print "======= front start success! =======" + else: + print "======= front start fail! =======" + sys.exit(0) + else: + print "======= front start fail! =======" + sys.exit(0) + return + else: + sys.exit(0) + if_success = 'Success' in result["output"] + if if_success: + print "======= front start success! =======" + else: + print "======= front start fail! =======" + sys.exit(0) + else: + print "======= front start fail! =======" + sys.exit(0) + print "============== front end... ==============" + return + +def stopFront(): + server_dir = currentDir + "/webase-front" + os.chdir(server_dir) + doCmdIgnoreException("source /etc/profile") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result = doCmd("./stop.sh") + if result["status"] == 0: + if_success = 'Success' in result["output"] + if if_success: + print "======= front stop success! =======" + else: + print "======= front is not running! =======" + else: + print "======= front stop fail! =======" + return diff --git a/deploy/comm/check.py b/deploy/comm/check.py new file mode 100644 index 0000000..33bfa76 --- /dev/null +++ b/deploy/comm/check.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import log as deployLog +import sys +from utils import * + +log = deployLog.getLogger() +checkDependent = ["git","openssl","curl","nginx"] + +def do(): + print "===================== envrionment check... =====================" + installRequirements() + checkSoft() + checkNodePort() + checkWebPort() + checkMgrPort() + checkFrontPort() + checkDbConnect() + print "===================== envrionment ready... =====================" + +def installRequirements(): + for require in checkDependent: + print "check {}...".format(require) + hasInstall = hasInstallServer(require) + if not hasInstall: + installByYum(require) + print "check finished Sucessfully." + return + +def checkSoft(): + print "check java..." + res1 = doCmdIgnoreException("java -version") + if res1["status"] != 0: + print " error! java is not install or configure!" + sys.exit(0) + print "check finished Sucessfully." + return + +def checkNodePort(): + print "check node port..." + deploy_ip = "127.0.0.1" + nodes = getCommProperties("node.counts") + node_counts = 2 + if nodes is not "nodeCounts": + node_counts = int(nodes) + node_rpcPort = int(getCommProperties("node.rpcPort")) + node_p2pPort = int(getCommProperties("node.p2pPort")) + node_channelPort = int(getCommProperties("node.channelPort")) + for i in range(node_counts): + res_rpcPort = net_if_used("127.0.0.1",node_rpcPort+i) + if res_rpcPort: + sys.exit(0) + res_p2pPort = net_if_used("127.0.0.1",node_p2pPort+i) + if res_p2pPort: + sys.exit(0) + res_channelPort = net_if_used("127.0.0.1",node_channelPort+i) + if res_channelPort: + sys.exit(0) + print "check finished Sucessfully." + return + +def checkWebPort(): + print "check web port..." + deploy_ip = "127.0.0.1" + web_port = getCommProperties("web.port") + res_web = net_if_used(deploy_ip,web_port) + if res_web: + sys.exit(0) + print "check finished Sucessfully." + return + +def checkMgrPort(): + print "check mgr port..." + deploy_ip = "127.0.0.1" + mgr_port = getCommProperties("mgr.port") + res_mgr = net_if_used(deploy_ip,mgr_port) + if res_mgr: + sys.exit(0) + print "check finished Sucessfully." + return + +def checkFrontPort(): + print "check front port..." + deploy_ip = "127.0.0.1" + front_port = getCommProperties("front.port") + res_front = net_if_used(deploy_ip,front_port) + if res_front: + sys.exit(0) + print "check finished Sucessfully." + return + +def checkDbConnect(): + print "check db connection..." + mysql_ip = getCommProperties("mysql.ip") + mysql_port = getCommProperties("mysql.port") + ifLink = do_telnet(mysql_ip,mysql_port) + if not ifLink: + print 'The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port) + sys.exit(0) + print "check finished Sucessfully." + return + +def checkSdkDir(): + print "checking sdk dir" + nodeDir = getCommProperties("node.sdkDir") + if not os.path.exists(nodeDir): + print "{} is not exists".format(nodeDir) + return + +def hasInstallServer(server): + result = doCmdIgnoreException("which {}".format(server)) + if result["status"] == 0: + return True + else: + return False + +def installByYum(server): + if isCentos(): + result = doCmd("sudo yum -y install {}".format(server)) + if result["status"] !=0: + os.system("yum install epel-release") + os.system("sudo yum install python-pip") + os.system("pip install --upgrade pip") + os.system("pip install requests") + result = doCmd("yum install {}".format(server)) + elif isSuse(): + os.system("sudo zypper install -y {}".format(server)) + elif isUbuntu(): + os.system("sudo apt-get install -y {}".format(server)) + else: + raise Exception("error,not support this platform,only support centos,suse,ubuntu.") + return + +if __name__ == '__main__': + pass diff --git a/deploy/comm/log.py b/deploy/comm/log.py new file mode 100644 index 0000000..8ffda8e --- /dev/null +++ b/deploy/comm/log.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import logging, os + +class Logger: + def __init__(self, path, clever=logging.DEBUG, Flevel=logging.DEBUG): + self.logger = logging.getLogger(path) + self.logger.setLevel(logging.DEBUG) + log_format = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S') + fh = logging.FileHandler(path) + fh.setFormatter(log_format) + fh.setLevel(Flevel) + self.logger.addHandler(fh) + + def debug(self, message): + self.logger.debug(message) + + def info(self, message): + self.logger.info(message) + + def infoPrint(self, mesage): + print mesage + self.logger.info(mesage) + + def war(self, message): + self.logger.warn(message) + + def error(self, message): + self.logger.error(message) + + def cri(self, message): + self.logger.critical(message) + + +loggermap = {} +def getLogger(): + logPath ="./log/" + logName="info.log" + isExists=os.path.exists(logPath) + if not isExists: + os.makedirs(logPath) + if logPath+logName in loggermap: + return loggermap[logPath+logName] + else: + logger = Logger(logPath+logName, logging.INFO, logging.INFO) + loggermap[logPath+logName] = logger + return logger + +if __name__ == '__main__': + log = getLogger() + log.info("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv") \ No newline at end of file diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py new file mode 100644 index 0000000..f018070 --- /dev/null +++ b/deploy/comm/mysql.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import log as deployLog +import sys +import MySQLdb as mdb +from utils import * + +log = deployLog.getLogger() + +def dbConnect(): + # get properties + mysql_ip = getCommProperties("mysql.ip") + mysql_port = int(getCommProperties("mysql.port")) + mysql_user = getCommProperties("mysql.user") + mysql_password = getCommProperties("mysql.password") + mysql_database = getCommProperties("mysql.database") + + try: + # connect + conn = mdb.connect(host=mysql_ip, port=mysql_port, user=mysql_user, passwd=mysql_password, charset='utf8') + conn.autocommit(1) + cursor = conn.cursor() + + # check db + result = cursor.execute('show databases like "%s"' %mysql_database) + drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) + create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) + if result == 1: + info = raw_input("mgr数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + if info == "y" or info == "Y": + log.info(drop_db) + cursor.execute(drop_db) + log.info(create_db) + cursor.execute(create_db) + else: + log.info(create_db) + cursor.execute(create_db) + cursor.close() + conn.close() + except: + import traceback + log.info(" mysql except {}".format(traceback.format_exc())) + traceback.print_exc() + sys.exit(0) + +if __name__ == '__main__': + pass diff --git a/deploy/comm/nginx.conf b/deploy/comm/nginx.conf new file mode 100644 index 0000000..09fcac2 --- /dev/null +++ b/deploy/comm/nginx.conf @@ -0,0 +1,60 @@ +user root; +worker_processes 1; + +error_log log_path/error.log; + +pid /run/nginx-webase-web.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log log_path/access.log ; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + add_header X-Frame-Options SAMEORIGIN; + upstream node_mgr_server{ + server 10.0.0.1:8083; + } + server { + listen 3002 default_server; + server_name 127.0.0.1; + location / { + root web_page_url; + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + + include /etc/nginx/default.d/*.conf; + + location /mgr { + proxy_pass http://node_mgr_server/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + error_page 404 /404.html; + location = /40x.html { + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + } + } + +} diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py new file mode 100644 index 0000000..3fa0a85 --- /dev/null +++ b/deploy/comm/utils.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import ConfigParser +import commands +import log as deployLog +import socket +import fcntl +import struct +import telnetlib +import os +import platform +import shutil +from distutils.dir_util import copy_tree + +log = deployLog.getLogger() +platformStr = platform.platform() + +def getIpAddress(ifname): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', ifname[:15]) + )[20:24]) + +def getLocalIp(): + return getIpAddress("eth0") + +def net_if_used(ip,port): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s.settimeout(0.5) + try: + result=s.connect_ex((ip, int(port))) + if result==0: + print " error! port {} has been used. please check.".format(port) + return True + else: + return False + finally: + s.close() + +def isUbuntu(): + return platformStr.lower().find("ubuntu") > -1 + +def isCentos(): + return platformStr.lower().find("centos") > -1 + +def isSuse(): + return platformStr.lower().find("suse") > -1 + +def getBaseDir(): + cwd = os.getcwd() + log.info(" os.getcwd() is {}".format(cwd)) + path = os.path.abspath(os.path.join(os.getcwd(), "..")) + return path + +def getCurrentBaseDir(): + cwd = os.getcwd() + log.info(" os.getcwd() is {}".format(cwd)) + path = os.path.abspath(os.path.join(os.getcwd(), ".")) + return path + +def copytree(src, dst): + copy_tree(src,dst) + return + +def doCmd(cmd): + log.info(" execute cmd start ,cmd : {}".format(cmd)) + result = dict() + (status, output) = commands.getstatusoutput(cmd) + result["status"] = status + result["output"] = output + log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd,status,output)) + if (0 != status): + raise Exception("execute cmd error ,cmd : {}, status is {} ,output is {}".format(cmd,status, output)) + return result + +def doCmdIgnoreException(cmd): + log.info(" execute cmd start ,cmd : {}".format(cmd)) + result = dict() + (status, output) = commands.getstatusoutput(cmd) + result["status"] = status + result["output"] = output + log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd, status, output)) + return result + +def getCommProperties(paramsKey): + current_dir = getCurrentBaseDir() + cf = ConfigParser.ConfigParser() + propertiesDir =current_dir+"/common.properties" + cf.read(propertiesDir) + log.info(" commProperties is {} ".format(propertiesDir)) + cf.sections() + value = cf.get('common', paramsKey) + return value + +def replaceConf(fileName,oldStr,newStr): + if not os.path.isfile(fileName): + print "{} is not a file ".format(fileName) + return + oldData ="" + with open(fileName, "r") as f: + for line in f: + if oldStr in line: + line = line.replace(oldStr, newStr) + oldData += line + with open(fileName, "w") as f: + f.write(oldData) + return + +def replaceConfDir(filePath,oldStr,newStr): + if not os.path.isdir(filePath): + print "{} is not a dir ".format(filePath) + return + for root, dirs, files in os.walk(filePath): + for file in files: + replaceConf(os.path.join(root,file),oldStr,newStr) + return + +def copyFiles(sourceDir, targetDir): + log.info(" copyFiles sourceDir: {} ".format(sourceDir)) + for f in os.listdir(sourceDir): + sourceF = os.path.join(sourceDir, f) + targetF = os.path.join(targetDir, f) + if os.path.isfile(sourceF): + # check dir + if not os.path.exists(targetDir): + os.makedirs(targetDir) + # copy file + shutil.copy(sourceF,targetF) + # check sub folder + if os.path.isdir(sourceF): + copyFiles(sourceF, targetF) + +def do_telnet(host,port): + try: + tn = telnetlib.Telnet(host, port, timeout=5) + tn.close() + except: + return False + return True + +def pullSourceExtract(urlName,fileName): + git_comm = "wget " + getCommProperties(urlName) + if not os.path.exists("{}/{}.zip".format(getCurrentBaseDir(),fileName)): + print git_comm + os.system(git_comm) + else: + info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + if info == "y" or info == "Y": + doCmd("rm -rf {}.zip".format(fileName)) + doCmd("rm -rf {}".format(fileName)) + print git_comm + os.system(git_comm) + if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): + doCmd("unzip -o {}.zip".format(fileName)) + if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): + print "{}.zip extract failed!".format(fileName) + sys.exit(0) + else: + info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + if info == "y" or info == "Y": + doCmd("rm -rf {}".format(fileName)) + doCmd("unzip -o {}.zip".format(fileName)) + if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): + print "{}.zip extract failed!".format(fileName) + sys.exit(0) + +def checkFileName(dir,fileName): + Files=os.listdir(dir) + for k in range(len(Files)): + Files[k]=os.path.splitext(Files[k])[0] + fileName = fileName + ".mv" + if fileName in Files: + return True + else: + return False + +if __name__ == '__main__': + print(getIpAddress("eth0")) + pass \ No newline at end of file diff --git a/deploy/common.properties b/deploy/common.properties new file mode 100644 index 0000000..376a691 --- /dev/null +++ b/deploy/common.properties @@ -0,0 +1,22 @@ +[common] +web.package.url=https://github.com/mingzhenliu/sss/releases/download/111/webase-web.zip +mgr.package.url=https://github.com/mingzhenliu/sss/releases/download/111/webase-node-mgr.zip +front.package.url=https://github.com/mingzhenliu/sss/releases/download/111/webase-front.zip + +mysql.ip=127.0.0.1 +mysql.port=3306 +mysql.user=dbUsername +mysql.password=dbPassword +mysql.database=db_mgr + +web.port=8080 +mgr.port=8081 +front.port=8082 + +fisco.version=2.0.0-rc2 +node.counts=nodeCounts +node.p2pPort=30300 +node.channelPort=20200 +node.rpcPort=8545 +front.h2.db=db_front +monitorDisk=/data \ No newline at end of file diff --git a/deploy/deploy.py b/deploy/deploy.py new file mode 100644 index 0000000..74d2a98 --- /dev/null +++ b/deploy/deploy.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# encoding: utf-8 +import sys +import comm.check as commCheck +import comm.build as commBuild +def do(): + if len(sys.argv)==1: + help() + return + param = sys.argv[1] + if "startAll" == param: + commCheck.do() + commBuild.do() + elif "stopAll" == param: + commBuild.end() + elif "startNode" == param: + commCheck.checkNodePort() + commBuild.startNode() + elif "stopNode" == param: + commBuild.stopNode() + elif "startWeb" == param: + commCheck.checkWebPort() + commBuild.startWeb() + elif "stopWeb" == param: + commBuild.stopWeb() + elif "startMgr" == param: + commCheck.checkMgrPort() + commBuild.startMgr() + elif "stopMgr" == param: + commBuild.stopMgr() + elif "startFront" == param: + commCheck.checkFrontPort() + commBuild.startFront() + elif "stopFront" == param: + commBuild.stopFront() + elif "check"== param: + commCheck.do() + elif "help"== param: + help() + else: + paramError() + return + +def help(): + helpMsg = ''' +Usage: python deploy [Parameter] + +Parameter: + check : check the environment + startAll : check the environment, deploy all server + stopAll : stop all server + startNode : start nodes + stopNode : stop nodes + startWeb : start web server + stopWeb : stop web server + startMgr : start mgr server + stopMgr : stop mgr server + startFront : start front server + stopFront : stop front server + +Attention: + 1. Need to install python2.7, jdk1.8.0_121+, mysql 5.6+, MySQL-python first + 2. Need to ensure a smooth network + 3. You need to install git, wget, nginx; if it is not installed, the installation script will automatically install these components, but this may fail. + ''' + print helpMsg + return + +def paramError(): + print "Param error! Please check." + print "" + help() + return + +if __name__ == '__main__': + do() + pass \ No newline at end of file diff --git a/install.md b/deploy/install.md similarity index 100% rename from install.md rename to deploy/install.md diff --git a/deploy/nodeconf b/deploy/nodeconf new file mode 100644 index 0000000..33faad1 --- /dev/null +++ b/deploy/nodeconf @@ -0,0 +1 @@ +127.0.0.1:nodeCounts agencyA 1,2 From 8fc3ec98413d51d271272cafaaf56777fb677bc7 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 14 Jun 2019 18:21:33 +0800 Subject: [PATCH 027/119] ./ change to bash --- deploy/comm/build.py | 26 +++++++++++++------------- deploy/install.md | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 311198c..51834e1 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -48,7 +48,7 @@ def startNode(): if not os.path.exists("{}/nodes".format(currentDir)): doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result_build = doCmd("./build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) if result_build["status"] == 0: if_build = 'completed' in result_build["output"] if not if_build: @@ -60,11 +60,11 @@ def startNode(): else: info = raw_input("节点目录nodes已经存在。是否重新安装?[y/n]:") if info == "y" or info == "Y": - doCmdIgnoreException("./nodes/127.0.0.1/stop_all.sh") + doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result_build = doCmd("./build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) if result_build["status"] == 0: if_build = 'completed' in result_build["output"] if not if_build: @@ -78,7 +78,7 @@ def startNode(): os.chdir(node_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - os.system("./start_all.sh") + os.system("bash start_all.sh") print "============== node end... ==============" return @@ -91,7 +91,7 @@ def stopNode(): os.chdir(node_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - os.system("./stop_all.sh") + os.system("bash stop_all.sh") return def changeWebConfig(): @@ -206,7 +206,7 @@ def startMgr(): os.chdir(script_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - dbResult = doCmd('./webase.sh {} {}'.format(mysql_ip, mysql_port)) + dbResult = doCmd('bash webase.sh {} {}'.format(mysql_ip, mysql_port)) if dbResult["status"] == 0: if_success = 'success' in dbResult["output"] if if_success: @@ -223,14 +223,14 @@ def startMgr(): doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result = doCmd("./start.sh") + result = doCmd("bash start.sh") if result["status"] == 0: if_started = 'started' in result["output"] if if_started: info = raw_input("mgr进程已经存在,是否kill进程强制安装?[y/n]:") if info == "y" or info == "Y": - doCmd("./stop.sh") - result_start = doCmd("./start.sh") + doCmd("bash stop.sh") + result_start = doCmd("bash start.sh") if result_start["status"] == 0: if_success = 'Success' in result_start["output"] if if_success: @@ -262,7 +262,7 @@ def stopMgr(): doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result = doCmd("./stop.sh") + result = doCmd("bash stop.sh") if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: @@ -325,13 +325,13 @@ def startFront(): doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result = doCmd("./start.sh") + result = doCmd("bash start.sh") if result["status"] == 0: if_started = 'started' in result["output"] if if_started: info = raw_input("front进程已经存在,是否kill进程强制安装?[y/n]:") if info == "y" or info == "Y": - doCmd("./stop.sh") + doCmd("bash stop.sh") result_start = doCmd("sh start.sh") if result_start["status"] == 0: if_success = 'Success' in result_start["output"] @@ -364,7 +364,7 @@ def stopFront(): doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result = doCmd("./stop.sh") + result = doCmd("bash stop.sh") if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: diff --git a/deploy/install.md b/deploy/install.md index 21d4e01..168bfba 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -32,9 +32,9 @@ unzip webase-deploy.zip cd webase-deploy ``` -## 3、修改配置(没有变化可以不修改) +## 3、修改配置 -① 可以使用以下命令修改,也可以直接修改文件(vi common.properties) +① 可以使用以下命令修改,也可以直接修改文件(vi common.properties),没有变化的可以不修改 ② 数据库需要提前安装(数据库安装请参看 [附录7.4](#74-数据库部署)) From 7094e05b195512b6bbd36e6431a160c35c67e3e2 Mon Sep 17 00:00:00 2001 From: dwusiq Date: Fri, 21 Jun 2019 16:05:03 +0800 Subject: [PATCH 028/119] =?UTF-8?q?1=E3=80=81=20add:=20quick-start=20proje?= =?UTF-8?q?ct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- images/architecture.png | Bin 0 -> 15978 bytes images/contract.png | Bin 0 -> 103214 bytes images/frontInfo.png | Bin 0 -> 78569 bytes images/keyUser.png | Bin 0 -> 54588 bytes images/monitor.png | Bin 0 -> 58426 bytes images/transHash.png | Bin 0 -> 72220 bytes quick-start.md | 120 ++++++++++++++++++ quick-start/.gitignore | 32 +++++ quick-start/build.gradle | 101 +++++++++++++++ .../java/com/webank/webase/Application.java | 27 ++++ .../webase/transaction/TransactionParam.java | 35 +++++ .../transaction/TransactionService.java | 62 +++++++++ .../src/main/resources/application.yml | 9 ++ quick-start/src/main/resources/log4j2.xml | 58 +++++++++ .../WebaseHelloworldApplicationTests.java | 16 +++ 15 files changed, 460 insertions(+) create mode 100644 images/architecture.png create mode 100644 images/contract.png create mode 100644 images/frontInfo.png create mode 100644 images/keyUser.png create mode 100644 images/monitor.png create mode 100644 images/transHash.png create mode 100644 quick-start.md create mode 100644 quick-start/.gitignore create mode 100644 quick-start/build.gradle create mode 100644 quick-start/src/main/java/com/webank/webase/Application.java create mode 100644 quick-start/src/main/java/com/webank/webase/transaction/TransactionParam.java create mode 100644 quick-start/src/main/java/com/webank/webase/transaction/TransactionService.java create mode 100644 quick-start/src/main/resources/application.yml create mode 100644 quick-start/src/main/resources/log4j2.xml create mode 100644 quick-start/src/test/java/com/webank/webase/helloworld/WebaseHelloworldApplicationTests.java diff --git a/images/architecture.png b/images/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf1f7e4813ad5f0a2a6f75d50c54594eb365247 GIT binary patch literal 15978 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 L`+B~bWzhcuLP$kP literal 0 HcmV?d00001 diff --git a/images/contract.png b/images/contract.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4c96ee9256c3515b9fd5d96cfc9bbdd47c4b00 GIT binary patch literal 103214 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 XST#Y><3S+MH>c zUP07_#DXYch|S*OkiMI)WPN-_(k|8-NZZ1bhzHb@R=Op5Phty3sVNY;OOdLz+u&oo z2u7_wihoXXn%6L+m#bX9Lw{U_&|1gZNuN1=I_f_ivEoWVE32Re?|E;CW^9GuCCi@NYeEX}G|PIH-6GBhkW@oUcF>|jdm`7J*L#tq?!Sg+b*%*@xn-*d#zABQ;kI1dqTgHP0=F;IS z6@iB`v5F0=wWER=L$=Rw_qDU6f1kcVN%kK?tFu(L*{_;Tyx%DRiY9EE$4B)_Y5@Pjk-Ze z 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 Z{mU{72K&q%%Rmc{Jzf1=);T3K0RUqB)rbH9 literal 0 HcmV?d00001 diff --git a/images/frontInfo.png b/images/frontInfo.png new file mode 100644 index 0000000000000000000000000000000000000000..3009a9c0df8333a26e8404ac102329b7fe4af9b8 GIT binary patch literal 78569 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 x+j%#cJO`j;e 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 Y_b<0kzVqR%kN;v%(C1ZuId<;f0ZLGz8vpE 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 CSdR$+ literal 0 HcmV?d00001 diff --git a/quick-start.md b/quick-start.md new file mode 100644 index 0000000..d6f01a5 --- /dev/null +++ b/quick-start.md @@ -0,0 +1,120 @@ +#使用WeBASE开发区块链应用 Step by Step + +## 1 部署WeBASE +请参考[快速部署](https://) + +## 2 登录WeBASE管理平台,添加节点信息,私钥信息等。 +* 节点信息: +![[节点]](./images/frontInfo.png) + +* 私钥用户: +![[私钥用户]](./images/keyUser.png) + +## 3 开发智能合约 +以HelloWorld.sol为例 +``` +pragma solidity ^0.4.2; + +contract HelloWorld{ + string name; + + function HelloWorld(){ + name = "Hello, World!"; + } + + function get()constant returns(string){ + return name; + } + + function set(string n){ + name = n; + } +} +``` + +* 通过智能合约IDE部署合约,并获取合约地址等信息 +![[合约]](./images/contract.png) + + + +## 4 根据所写合约和交易api的格式,发送交易。 +详细信息请参考[交易接口](https://) + + +* 请求实现主要代码: + - application.yml +``` +transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle +groupId: 1 +userAddress: "0x4f08eac5af5e77b7006d11bee94adba2f721def8" +useAes: true +contract.name: HelloWorld +contract.address: "0xca597170829f4ad5054b618425a56e0be23cbc55" +contract.funcName: set +contract.funcParam: "[\"abc\"]" +``` + - TransactionService.java +``` +@Slf4j +@Data +@Service +public class TransactionService { + @Autowired + private RestTemplate rest; + @Value("${transactionUrl}") + private String url; + @Value("${userAddress}") + private String user; + @Value("${groupId}") + private int groupId; + @Value("${useAes}") + private Boolean useAes; + @Value("${contract.name}") + private String contractName; + @Value("${contract.address}") + private String contractAddress; + @Value("${contract.funcName}") + private String funcName; + @Value("${contract.funcParam}") + private String funcParam; + + public void sendTransaction() { + + try { + TransactionParam transParam = new TransactionParam(); + transParam.setGroupId(groupId); + transParam.setContractAddress(contractAddress); + transParam.setUseAes(useAes); + transParam.setUser(user); + transParam.setContractName(contractName); + transParam.setFuncName(funcName); + transParam.setFuncParam(JSONArray.parseArray(funcParam)); + + log.info("transaction param:{}", JSON.toJSONString(transParam)); + Object rsp = rest.postForObject(url, transParam, Object.class); + String rspStr = "null"; + if (Objects.nonNull(rsp)) { + rspStr = JSON.toJSONString(rsp); + } + log.info("transaction result:{}", rspStr); + } catch (Exception ex) { + log.error("fail sendTransaction", ex); + } + System.exit(1); + } +} +``` + + + +## 5 在线运维管理 +* 查看交易解析 +![[交易解析]](./images/transHash.png) + +* 交易审计 +![[交易审计]](./images/monitor.png) + + + + + diff --git a/quick-start/.gitignore b/quick-start/.gitignore new file mode 100644 index 0000000..6c01878 --- /dev/null +++ b/quick-start/.gitignore @@ -0,0 +1,32 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/quick-start/build.gradle b/quick-start/build.gradle new file mode 100644 index 0000000..c279144 --- /dev/null +++ b/quick-start/build.gradle @@ -0,0 +1,101 @@ +group = 'com.webank.webase' +version = '0.0.1-SNAPSHOT' + +apply plugin: 'maven' +apply plugin: 'java' +apply plugin: 'idea' +apply plugin: 'eclipse' + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + +// In this section you declare where to find the dependencies of your project +repositories { + maven {url "http://maven.aliyun.com/nexus/content/groups/public/"} + + mavenLocal() + mavenCentral() +} + +def springboot_version="2.1.2.RELEASE" +List springboot = [ + "org.springframework.boot:spring-boot-starter-web:$springboot_version", + "org.springframework.boot:spring-boot-starter-log4j2:$springboot_version" +] + + +List lombok = [ + "org.projectlombok:lombok:1.18.2" +] + +List test = [ + "org.springframework.boot:spring-boot-starter-test:$springboot_version" +] + + +dependencies { + compile springboot + // compile "org.apache.commons:commons-lang3:3.8.1" + compile "com.alibaba:fastjson:1.2.54" + + compileOnly lombok + + testCompile test +} + +configurations { + all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + all*.exclude group: 'org.slf4j', module: 'slf4j-log4j12' + all*.exclude group: 'log4j', module: 'log4j' + all*.exclude group: 'org.ow2.asm', module: 'asm' + all*.exclude group: 'jline', module: 'jline' + all*.exclude group: 'com.google.protobuf', module: 'protobuf-java' + all*.exclude group: 'javax.annotation', module: 'javax.annotation-api' +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + resources { + srcDir 'src/main/resources' + } + } +} + +clean { + delete 'dist' + delete 'build' +} + + +jar { + destinationDir file('dist/apps') + archiveName project.name + '.jar' + exclude '**/*.xml' + exclude '**/*.yml' + exclude '**/*.properties' + + doLast { + copy { + from file('src/main/resources/') + into 'dist/conf' + } + copy { + from file('script/') + into 'dist/script' + } + copy { + from configurations.runtime + into 'dist/lib' + } + copy { + from file('.').listFiles().findAll{File f -> (f.name.endsWith('.bat') || f.name.endsWith('.sh') || f.name.endsWith('.env'))} + into 'dist' + } + } +} diff --git a/quick-start/src/main/java/com/webank/webase/Application.java b/quick-start/src/main/java/com/webank/webase/Application.java new file mode 100644 index 0000000..ef32311 --- /dev/null +++ b/quick-start/src/main/java/com/webank/webase/Application.java @@ -0,0 +1,27 @@ +package com.webank.webase; + +import com.webank.webase.transaction.TransactionService; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); + TransactionService tranService = applicationContext.getBean(TransactionService.class); + tranService.sendTransaction(); + } + + @Bean + public RestTemplate getRestTemplate() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setReadTimeout(10000);// ms + factory.setConnectTimeout(10000);// ms + return new RestTemplate(factory); + } +} diff --git a/quick-start/src/main/java/com/webank/webase/transaction/TransactionParam.java b/quick-start/src/main/java/com/webank/webase/transaction/TransactionParam.java new file mode 100644 index 0000000..d41d88e --- /dev/null +++ b/quick-start/src/main/java/com/webank/webase/transaction/TransactionParam.java @@ -0,0 +1,35 @@ +/** + * Copyright 2014-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.webank.webase.transaction; + +import java.util.List; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * param of send transaction. + */ +@Data +@NoArgsConstructor +public class TransactionParam { + + private Integer groupId; + private String user; + private String contractName; + private String funcName; + private String contractAddress; + private Boolean useAes; + private List funcParam; +} \ No newline at end of file diff --git a/quick-start/src/main/java/com/webank/webase/transaction/TransactionService.java b/quick-start/src/main/java/com/webank/webase/transaction/TransactionService.java new file mode 100644 index 0000000..20326fa --- /dev/null +++ b/quick-start/src/main/java/com/webank/webase/transaction/TransactionService.java @@ -0,0 +1,62 @@ +package com.webank.webase.transaction; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.Objects; + + +@Slf4j +@Data +@Service +public class TransactionService { + @Autowired + private RestTemplate rest; + @Value("${transactionUrl}") + private String url; + @Value("${userAddress}") + private String user; + @Value("${groupId}") + private int groupId; + @Value("${useAes}") + private Boolean useAes; + @Value("${contract.name}") + private String contractName; + @Value("${contract.address}") + private String contractAddress; + @Value("${contract.funcName}") + private String funcName; + @Value("${contract.funcParam}") + private String funcParam; + + public void sendTransaction() { + + try { + TransactionParam transParam = new TransactionParam(); + transParam.setGroupId(groupId); + transParam.setContractAddress(contractAddress); + transParam.setUseAes(useAes); + transParam.setUser(user); + transParam.setContractName(contractName); + transParam.setFuncName(funcName); + transParam.setFuncParam(JSONArray.parseArray(funcParam)); + + log.info("transaction param:{}", JSON.toJSONString(transParam)); + Object rsp = rest.postForObject(url, transParam, Object.class); + String rspStr = "null"; + if (Objects.nonNull(rsp)) { + rspStr = JSON.toJSONString(rsp); + } + log.info("transaction result:{}", rspStr); + } catch (Exception ex) { + log.error("fail sendTransaction", ex); + } + System.exit(1); + } +} diff --git a/quick-start/src/main/resources/application.yml b/quick-start/src/main/resources/application.yml new file mode 100644 index 0000000..dfa9c85 --- /dev/null +++ b/quick-start/src/main/resources/application.yml @@ -0,0 +1,9 @@ +transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle +groupId: 1 +userAddress: "0x4f08eac5af5e77b7006d11bee94adba2f721def8" +useAes: true +contract: + name: HelloWorld + address: "0xca597170829f4ad5054b618425a56e0be23cbc55" + funcName: set + funcParam: "[\"abc\"]" \ No newline at end of file diff --git a/quick-start/src/main/resources/log4j2.xml b/quick-start/src/main/resources/log4j2.xml new file mode 100644 index 0000000..3c85f57 --- /dev/null +++ b/quick-start/src/main/resources/log4j2.xml @@ -0,0 +1,58 @@ + + + + + + ./logs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-start/src/test/java/com/webank/webase/helloworld/WebaseHelloworldApplicationTests.java b/quick-start/src/test/java/com/webank/webase/helloworld/WebaseHelloworldApplicationTests.java new file mode 100644 index 0000000..fb30646 --- /dev/null +++ b/quick-start/src/test/java/com/webank/webase/helloworld/WebaseHelloworldApplicationTests.java @@ -0,0 +1,16 @@ +package com.webank.webase.helloworld; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class WebaseHelloworldApplicationTests { + + @Test + public void contextLoads() { + } + +} From 5768169f139feece790fd5af87a48d89e2c36f72 Mon Sep 17 00:00:00 2001 From: dwusiq Date: Fri, 21 Jun 2019 16:13:35 +0800 Subject: [PATCH 029/119] update description of trancaction interface --- quick-start.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quick-start.md b/quick-start.md index d6f01a5..cac53f3 100644 --- a/quick-start.md +++ b/quick-start.md @@ -1,4 +1,4 @@ -#使用WeBASE开发区块链应用 Step by Step +#使用WeBASE开发区块链应用 quick start ## 1 部署WeBASE 请参考[快速部署](https://) @@ -41,8 +41,8 @@ contract HelloWorld{ 详细信息请参考[交易接口](https://) -* 请求实现主要代码: - - application.yml +### 4.1 接口调用的主要代码: +* application.yml ``` transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle groupId: 1 @@ -53,7 +53,7 @@ contract.address: "0xca597170829f4ad5054b618425a56e0be23cbc55" contract.funcName: set contract.funcParam: "[\"abc\"]" ``` - - TransactionService.java +* TransactionService.java ``` @Slf4j @Data From 8a1865568ff4e67b01098e9cca1cbeeccf7c81fb Mon Sep 17 00:00:00 2001 From: dwusiq Date: Fri, 21 Jun 2019 17:18:49 +0800 Subject: [PATCH 030/119] update pitcher path --- README.md | 2 +- architecture.png | Bin 15978 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 architecture.png diff --git a/README.md b/README.md index 197eebf..0522219 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务 # 整体架构 -![[架构图]](./architecture.png) +![[架构图]](./images/architecture.png) # 安装说明 diff --git a/architecture.png b/architecture.png deleted file mode 100644 index 7cf1f7e4813ad5f0a2a6f75d50c54594eb365247..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15978 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 L`+B~bWzhcuLP$kP From 77112a31cda2d87301bb76e1f73b33bc4807ac85 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Fri, 21 Jun 2019 17:23:05 +0800 Subject: [PATCH 031/119] Update quick-start.md update doc --- quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quick-start.md b/quick-start.md index cac53f3..67f6dbe 100644 --- a/quick-start.md +++ b/quick-start.md @@ -1,4 +1,4 @@ -#使用WeBASE开发区块链应用 quick start +# 使用WeBASE开发区块链应用 quick start ## 1 部署WeBASE 请参考[快速部署](https://) From e99e0ca545d514a3ba375a382879e912630b20c7 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Mon, 24 Jun 2019 18:50:22 +0800 Subject: [PATCH 032/119] Update quick-start.md --- quick-start.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/quick-start.md b/quick-start.md index 67f6dbe..60d91bf 100644 --- a/quick-start.md +++ b/quick-start.md @@ -1,9 +1,11 @@ -# 使用WeBASE开发区块链应用 quick start +# 使用WeBASE开发区块链应用 ## 1 部署WeBASE -请参考[快速部署](https://) +搭建WeBASE, 请参考[快速部署](https://) + +## 2 登录WeBASE管理平台进行配置 +安装WeBASE完成后,需要将节点信息添加到WeBASE平台中,这样WeBASE才可和节点进行通信。需要添加的信息包含节点信息,生成用户的私钥等。如下图所示: -## 2 登录WeBASE管理平台,添加节点信息,私钥信息等。 * 节点信息: ![[节点]](./images/frontInfo.png) @@ -32,16 +34,19 @@ contract HelloWorld{ } ``` -* 通过智能合约IDE部署合约,并获取合约地址等信息 +* 通过智能合约IDE部署合约,并获取合约地址等信息, ![[合约]](./images/contract.png) -## 4 根据所写合约和交易api的格式,发送交易。 -详细信息请参考[交易接口](https://) +## 4 应用层开发 + +### 4.1 根据所写合约和交易api的格式,发送交易。 +请参考[交易接口](https://) +从IDE中的输出信息,拷贝合约地址,合约名,方法名等信息,同时获取用户的公钥信息,调用交易接口。具体代码请参考[HelloWorld范例] (https://github.com/WeBankFinTech/WeBASE/tree/master/quick-start) -### 4.1 接口调用的主要代码: +### 4.2 接口调用的主要代码: * application.yml ``` transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle @@ -105,14 +110,11 @@ public class TransactionService { } ``` - - -## 5 在线运维管理 +## 5 运维管理 +应用层发布后,持续发送交易,可在WeBASE管理平台查看数据概览,节点监控,查看交易解析,交易审计等管理功能。 * 查看交易解析 ![[交易解析]](./images/transHash.png) -* 交易审计 -![[交易审计]](./images/monitor.png) From a7061bec0ecaa972b60d22fbd5ad4b2b34bdfa72 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Mon, 24 Jun 2019 18:53:23 +0800 Subject: [PATCH 033/119] Update quick-start.md --- quick-start.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quick-start.md b/quick-start.md index 60d91bf..d667a73 100644 --- a/quick-start.md +++ b/quick-start.md @@ -44,8 +44,8 @@ contract HelloWorld{ ### 4.1 根据所写合约和交易api的格式,发送交易。 请参考[交易接口](https://) -从IDE中的输出信息,拷贝合约地址,合约名,方法名等信息,同时获取用户的公钥信息,调用交易接口。具体代码请参考[HelloWorld范例] (https://github.com/WeBankFinTech/WeBASE/tree/master/quick-start) - +从IDE中的输出信息,拷贝合约地址,合约名,方法名等信息,同时获取用户的公钥信息,调用交易接口。 +具体代码请参考 [HelloWorld范例](https://github.com/WeBankFinTech/WeBASE/tree/master/quick-start) ### 4.2 接口调用的主要代码: * application.yml ``` From 6cb7cd36778c98f2b68c89b85c6fcd70fcd937af Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Mon, 24 Jun 2019 21:08:17 +0800 Subject: [PATCH 034/119] Update README.md --- README.md | 114 ++++++++++++++++++++---------------------------------- 1 file changed, 42 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 0522219..37e6a4a 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,51 @@ -# 介绍 -WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和底层之间搭建的一套通用组件,围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。可以屏蔽区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率。包含了节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 - -开发者搭建完区块链节点后,部署WeBASE,基于WeBASE开发区块链应用,将会大幅提升效率。 - - - -# 设计原则 +# 什么是WeBASE + +WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 + +WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://) + +## 整体架构 +完整的部署架构如下,其中webase-front需要和区块链节点同机部署。 +![[架构图]](../../images/webase/architecture.png) + +## 各子系统简介 +* 节点前置服务 **[WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** +``` +集成web3jsdk,提供restful风格的接口,客户端可以使用http的形式和节点进行交互,内置内存数据库,采集节点健康度数据。内置web控制台,实现节点的可视化操作。 +``` +* 节点管理服务 **[WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** +``` +处理前端页面所有web请求,管理各个节点的状态,管理链上所有智能合约,对区块链的数据进行统计、分析,对异常交易的审计,私钥管理等。 +``` +* WeBASE管理平台 **[WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** +``` +可视化操作平台,可基于此平台查看节点信息,开发智能合约等。 +``` +* 交易服务 **[WeBASE-Transcation](https://github.com/WeBankFinTech/WeBASE-Transcation)** +``` +接收交易请求,缓存交易到数据库中,异步上链,可大幅提升吞吐量,解决区块链的tps瓶颈。 +``` +* 私钥托管和签名服务 **[WeBASE-Sign](https://github.com/WeBankFinTech/WeBASE-Sign)** +``` +托管用户私钥,提供云端签名。 +``` +* 数据导出代码生成工具 **[WeBASE-Codegen-Monkey](https://github.com/WeBankFinTech/WeBASE-Codegen-Monkey)** +``` +代码生成工具,通过配置可以生成数据导出的核心代码。 +``` +* 数据导出服务 **[WeBASE-Collect-Bee](https://github.com/WeBankFinTech/WeBASE-Collect-Bee)** +``` +导出区块链上的基础数据,如当前块高、交易总量等,通过智能合约的配置,导出区块链上合约的业务数据,包括event、构造函数、合约地址、执行函数的信息等。 +``` +## 设计原则 **按需部署** WeBASE抽象应用开发的诸多共性模块,形成各类服务组件,开发者根据需要部署所需组件。 **微服务** -WeWeBASE采用微服务架构,基于spring-boot框架,提供Restful风格接口。 +WeBASE采用微服务架构,基于spring-boot框架,提供Restful风格接口。 **零耦合** -WeBWeBASE所有子系统独立存在,均可独立部署,独立提供服务。 +WeBASE所有子系统独立存在,均可独立部署,独立提供服务。 **可定制** 前端页面往往带有自身的业务属性,因此WeBASE采用前后端分离的技术,便于开发者基于后端接口定制自己的前端页面。 - -# 整体架构 - -![[架构图]](./images/architecture.png) - - -# 安装说明 -请参考安装说明文档[install.md](https://github.com/WeBankFinTech/WeBASE/blob/dev/install.md) - -# 各子系统介绍 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
子系统名功能介绍
webase-front节点前置集成web3jsdk,提供restful风格的接口,客户端可以使用http的形式和节点进行交互,如获取节点快高,部署合约,发送交易等。内置内存数据库,采集节点健康度数据。内置web控制台,实现节点的可视化操作。
webase-transcation交易上链代理接收交易请求,缓存交易到数据库中,异步上链。可大幅提升吞吐量,解决区块链的tps瓶颈。
webase-node-mgr节点管理处理前端页面所有web请求,管理各个节点的状态,管理链上所有智能合约,对区块链的数据进行统计、分析,对异常交易的审计,私钥管理等;
webase-webWeBASE管理平台数据概览:可以查看机构、节点、合约、区块和交易详情。节点管理:查看区块链上所有节点的状态。合约管理:编辑、编译、部署、调试、测试合约。私钥管理:管理各用户的公私钥。系统监控:各节点的监控数据查看。交易审计:异常交易事后审计。
- - -# 应用开发步骤 -1 部署WeBASE。 - -2 登录WeBASE管理平台,添加节点信息,私钥信息等。 - -3 开发智能合约,编译、部署、测试合约。 - -4 根据所写合约和交易api的格式,发送交易。 - -5 登录管理平台查看交易详情,查看交易统计信息,在线运维管理 - -# 贡献说明 -请阅读我们的贡献[文档](https://github.com/WeBankFinTech/WeBASE/blob/master/CONTRIBUTING.md),了解如何贡献代码,并提交你的贡献。 - -希望在您的参与下,WeBASE会越来越好! - -# 社区 -- 联系我们:webase@webank.com From 6a64f434f9be0661474cde1c01b877dd50bbd68b Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Mon, 24 Jun 2019 21:10:25 +0800 Subject: [PATCH 035/119] Update README.md --- README.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/README.md b/README.md index 37e6a4a..d43bd6a 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,7 @@ WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 -WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://) - -## 整体架构 -完整的部署架构如下,其中webase-front需要和区块链节点同机部署。 -![[架构图]](../../images/webase/architecture.png) +WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE/blob/master/quick-start.md) ## 各子系统简介 * 节点前置服务 **[WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** @@ -37,15 +33,3 @@ WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需 ``` 导出区块链上的基础数据,如当前块高、交易总量等,通过智能合约的配置,导出区块链上合约的业务数据,包括event、构造函数、合约地址、执行函数的信息等。 ``` -## 设计原则 -**按需部署** -WeBASE抽象应用开发的诸多共性模块,形成各类服务组件,开发者根据需要部署所需组件。 - -**微服务** -WeBASE采用微服务架构,基于spring-boot框架,提供Restful风格接口。 - -**零耦合** -WeBASE所有子系统独立存在,均可独立部署,独立提供服务。 - -**可定制** -前端页面往往带有自身的业务属性,因此WeBASE采用前后端分离的技术,便于开发者基于后端接口定制自己的前端页面。 From 627240711a470a22620483c63418c8be9c20db48 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Mon, 24 Jun 2019 21:18:28 +0800 Subject: [PATCH 036/119] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d43bd6a..103561e 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,11 @@ WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需 ``` 导出区块链上的基础数据,如当前块高、交易总量等,通过智能合约的配置,导出区块链上合约的业务数据,包括event、构造函数、合约地址、执行函数的信息等。 ``` + +## 贡献说明 +请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 + +希望在您的参与下,WeBASE会越来越好! + +## 社区 +联系我们:webase@webank.com From a909ac6eddd09c37bc298fd6255979924a78495e Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Mon, 24 Jun 2019 21:20:57 +0800 Subject: [PATCH 037/119] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 103561e..589e2d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 什么是WeBASE -WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。 +WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。详细介绍请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE/blob/master/quick-start.md) From 2eabea8681aadc8345f556a9dd94280edbd6c0b5 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 25 Jun 2019 12:05:07 +0800 Subject: [PATCH 038/119] modify url V1.0.0 --- deploy/comm/build.py | 4 ++-- deploy/common.properties | 6 +++--- deploy/install.md | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 51834e1..efd3273 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -16,7 +16,7 @@ def do(): startMgr() startFront() print "===================== deploy end... =====================" - print "===================== version dev-0.8 =====================" + print "===================== version V1.0.0 =====================" return def end(): @@ -294,7 +294,7 @@ def changeFrontConfig(): # change server config doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s/8081/{}/g" {}/application.yml'.format(frontPort, server_dir)) - doCmd('sed -i "s/10.0.0.1:8080/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) + doCmd('sed -i "s/127.0.0.1:8080/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) doCmd('sed -i "s%front_db%{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(monitorDisk, server_dir)) diff --git a/deploy/common.properties b/deploy/common.properties index 376a691..359fffc 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,7 @@ [common] -web.package.url=https://github.com/mingzhenliu/sss/releases/download/111/webase-web.zip -mgr.package.url=https://github.com/mingzhenliu/sss/releases/download/111/webase-node-mgr.zip -front.package.url=https://github.com/mingzhenliu/sss/releases/download/111/webase-front.zip +web.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-web.zip +mgr.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-node-mgr.zip +front.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-front.zip mysql.ip=127.0.0.1 mysql.port=3306 diff --git a/deploy/install.md b/deploy/install.md index 168bfba..c2d7d5b 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -2,7 +2,7 @@ [TOC] -​ 一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置(webase-front)、节点管理(webase-node-mgr)、管理平台(webase-web)。 +​ 一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置(WeBASE-Front)、节点管理(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 ​ 部署脚本会拉取相关安装包进行部署(需保持网络畅通),重复部署可根据提示进行相关操作。 @@ -273,7 +273,7 @@ Saving to: ‘webase-front.zip’ 0% [ ] 77,974 37.8KB/s ``` -答:部署过程会下载工程编译包,可能会因为网络原因导致过慢。此时,可以先手动下载( [webase-web](https://github.com/mingzhenliu/sss/releases/download/111/webase-web.zip) 、[webase-node-mgr](https://github.com/mingzhenliu/sss/releases/download/111/webase-node-mgr.zip) 、[webase-front](https://github.com/mingzhenliu/sss/releases/download/111/webase-front.zip)),再上传至服务器webase-deploy目录,在部署过程中根据提示不再重新下载编译包。 +答:部署过程会下载工程编译包,可能会因为网络原因导致过慢。此时,可以先手动下载( [webase-web](https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-web.zip) 、[webase-node-mgr](https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-node-mgr.zip) 、[webase-front](https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-front.zip)),再上传至服务器webase-deploy目录,在部署过程中根据提示不再重新下载编译包。 ### 8.4 部署时数据库访问报错 From 8c16d65f2d7314f3979960c46aa5e0710b81eec3 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 25 Jun 2019 12:10:33 +0800 Subject: [PATCH 039/119] modify url V1.0.0 --- deploy/install.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/install.md b/deploy/install.md index c2d7d5b..f8862c0 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -2,7 +2,7 @@ [TOC] -​ 一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置(WeBASE-Front)、节点管理(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 +​ 一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 ​ 部署脚本会拉取相关安装包进行部署(需保持网络畅通),重复部署可根据提示进行相关操作。 @@ -47,9 +47,9 @@ cd webase-deploy 数据库密码:sed -i "s%dbPassword%${your_db_password}%g" common.properties 数据库名称:sed -i "s%db_mgr%${your_db_name}%g" common.properties -web服务端口:sed -i "s%8080%${your_web_port}%g" common.properties -mgr服务端口:sed -i "s%8081%${your_mgr_port}%g" common.properties -front服务端口:sed -i "s%8082%${your_front_port}%g" common.properties +管理平台服务端口:sed -i "s%8080%${your_web_port}%g" common.properties +节点管理子系统服务端口:sed -i "s%8081%${your_mgr_port}%g" common.properties +节点前置子系统服务端口:sed -i "s%8082%${your_front_port}%g" common.properties 节点fisco版本:sed -i "s%2.0.0-rc2%${your_fisco_version}%g" common.properties 节点安装个数:sed -i "s%nodeCounts%${your_node_counts}%g" common.properties @@ -224,7 +224,7 @@ mysql -utest -p123456 -h 127.0.0.1 -P 3306 创建数据库 ```sql -mysql > create database db_browser; +mysql > create database db_mgr; ``` ## 8、常见问题 From d3efc24eed1abc38cfa80ec18ff265233bad8529 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 25 Jun 2019 12:12:50 +0800 Subject: [PATCH 040/119] modify url V1.0.0 --- deploy/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/install.md b/deploy/install.md index f8862c0..ed31430 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -21,7 +21,7 @@ 获取部署安装包: ```shell -wget https://github.com/mingzhenliu/sss/releases/download/111/webase-deploy.zip +wget https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-deploy.zip ``` 解压安装包: ```shell From 44ea8bffcd9fb5fe1dacab7b4c49a83ec6997d0c Mon Sep 17 00:00:00 2001 From: wusiq Date: Tue, 25 Jun 2019 15:12:16 +0800 Subject: [PATCH 041/119] change path of quick-start.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 589e2d3..820852c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。详细介绍请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) -WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE/blob/master/quick-start.md) +WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) ## 各子系统简介 * 节点前置服务 **[WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** From 92db7801f1ae6aff17dc0112f0e1bd0f34d2cb49 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 25 Jun 2019 16:52:27 +0800 Subject: [PATCH 042/119] modify front url --- deploy/install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/install.md b/deploy/install.md index ed31430..8e8a225 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -89,7 +89,7 @@ http://{deployIP}:{webPort} 节点前置控制台: ``` -http://{deployIP}:{frontPort}/webase-front +http://{deployIP}:{frontPort}/WeBASE-Front ``` **备注:**部署服务器IP和相关服务端口需对应修改 @@ -259,7 +259,7 @@ Traceback (most recent call last): ImportError: No module named MySQLdb ``` -答:MySQL-python安装请参看部署附录7.3 +答:MySQL-python安装请参看部署 [附录7.3](#73-MySQL-python部署) ### 8.3 部署时编译包下载慢 From e4dfa9ad496f644244a3fcc28b7e931007ca55af Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 25 Jun 2019 19:23:14 +0800 Subject: [PATCH 043/119] Update README.md --- README.md | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 820852c..bc5231b 100644 --- a/README.md +++ b/README.md @@ -5,34 +5,26 @@ WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应 WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) ## 各子系统简介 -* 节点前置服务 **[WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** -``` +* **节点前置服务 [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** 集成web3jsdk,提供restful风格的接口,客户端可以使用http的形式和节点进行交互,内置内存数据库,采集节点健康度数据。内置web控制台,实现节点的可视化操作。 -``` -* 节点管理服务 **[WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** -``` + +* **节点管理服务 [WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** 处理前端页面所有web请求,管理各个节点的状态,管理链上所有智能合约,对区块链的数据进行统计、分析,对异常交易的审计,私钥管理等。 -``` -* WeBASE管理平台 **[WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** -``` + +* **WeBASE管理平台 [WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** 可视化操作平台,可基于此平台查看节点信息,开发智能合约等。 -``` -* 交易服务 **[WeBASE-Transcation](https://github.com/WeBankFinTech/WeBASE-Transcation)** -``` + +* **交易服务 [WeBASE-Transcation](https://github.com/WeBankFinTech/WeBASE-Transcation)** 接收交易请求,缓存交易到数据库中,异步上链,可大幅提升吞吐量,解决区块链的tps瓶颈。 -``` -* 私钥托管和签名服务 **[WeBASE-Sign](https://github.com/WeBankFinTech/WeBASE-Sign)** -``` + +* **私钥托管和签名服务 [WeBASE-Sign](https://github.com/WeBankFinTech/WeBASE-Sign)** 托管用户私钥,提供云端签名。 -``` -* 数据导出代码生成工具 **[WeBASE-Codegen-Monkey](https://github.com/WeBankFinTech/WeBASE-Codegen-Monkey)** -``` +* **数据导出代码生成工具 [WeBASE-Codegen-Monkey](https://github.com/WeBankFinTech/WeBASE-Codegen-Monkey)** 代码生成工具,通过配置可以生成数据导出的核心代码。 -``` -* 数据导出服务 **[WeBASE-Collect-Bee](https://github.com/WeBankFinTech/WeBASE-Collect-Bee)** -``` + +* **数据导出服务 [WeBASE-Collect-Bee](https://github.com/WeBankFinTech/WeBASE-Collect-Bee)** 导出区块链上的基础数据,如当前块高、交易总量等,通过智能合约的配置,导出区块链上合约的业务数据,包括event、构造函数、合约地址、执行函数的信息等。 -``` + ## 贡献说明 请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 From 6fea386686c5817e99be2dbdfb828762bc80b72f Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Wed, 26 Jun 2019 10:49:24 +0800 Subject: [PATCH 044/119] readme format --- deploy/install.md | 296 ++-------------------------------------------- 1 file changed, 7 insertions(+), 289 deletions(-) diff --git a/deploy/install.md b/deploy/install.md index 8e8a225..89bb564 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -1,293 +1,11 @@ # 一键部署说明 +## 简介 +一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) -[TOC] +## 贡献说明 +请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 -​ 一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 +希望在您的参与下,WeBASE会越来越好! -​ 部署脚本会拉取相关安装包进行部署(需保持网络畅通),重复部署可根据提示进行相关操作。 - -## 1、前提条件 - -| 环境 | 版本 | -| ------ | ---------------------- | -| Java | jdk1.8.0_121或以上版本 | -| python | 2.7 | -| MySQL-python | 1.2.5 | -| mysql | mysql-5.6或以上版本 | - -**备注:** 安装说明请参看 [附录7](#7附录) - -## 2、拉取部署脚本 - -获取部署安装包: -```shell -wget https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-deploy.zip -``` -解压安装包: -```shell -unzip webase-deploy.zip -``` -进入目录: -```shell -cd webase-deploy -``` - -## 3、修改配置 - -① 可以使用以下命令修改,也可以直接修改文件(vi common.properties),没有变化的可以不修改 - -② 数据库需要提前安装(数据库安装请参看 [附录7.4](#74-数据库部署)) - -③ 服务端口不能小于1024 - -```shell -数据库ip:sed -i "s%127.0.0.1%${your_db_ip}%g" common.properties -数据库端口:sed -i "s%3306%${your_db_port}%g" common.properties -数据库用户名:sed -i "s%dbUsername%${your_db_account}%g" common.properties -数据库密码:sed -i "s%dbPassword%${your_db_password}%g" common.properties -数据库名称:sed -i "s%db_mgr%${your_db_name}%g" common.properties - -管理平台服务端口:sed -i "s%8080%${your_web_port}%g" common.properties -节点管理子系统服务端口:sed -i "s%8081%${your_mgr_port}%g" common.properties -节点前置子系统服务端口:sed -i "s%8082%${your_front_port}%g" common.properties - -节点fisco版本:sed -i "s%2.0.0-rc2%${your_fisco_version}%g" common.properties -节点安装个数:sed -i "s%nodeCounts%${your_node_counts}%g" common.properties -节点p2p端口:sed -i "s%30300%${your_p2p_port}%g" common.properties -节点channel端口:sed -i "s%20200%${your_channel_port}%g" common.properties -节点rpc端口:sed -i "s%8545%${your_rpc_port}%g" common.properties -前置h2数据库名:sed -i "s%/db_front%${your_dist_dir}%g" common.properties -前置要监控的磁盘路径:sed -i "s%/data%${your_dist_dir}%g" common.properties - -例子(将磁盘路径由/data改为/home):sed -i "s%/data%/home%g" common.properties -``` - -## 4、部署 -部署所有服务: -```shell -python deploy.py startAll -``` -停止所有服务: -```shell -python deploy.py stopAll -``` -单独启停命令和说明可查看帮助: -```shell -python deploy.py help -``` - -**备注:** 部署过程出现问题可以查看 [常见问题8](#8常见问题) - -## 5、访问 - -管理平台: - -``` -http://{deployIP}:{webPort} -``` - -节点前置控制台: - -``` -http://{deployIP}:{frontPort}/WeBASE-Front -``` - -**备注:**部署服务器IP和相关服务端口需对应修改 - -## 6、日志路径 - -``` -部署日志:log/ -节点日志:nodes/127.0.0.1/node*/log/ -web服务日志:webase-web/log/ -mgr服务日志:webase-node-mgr/logs/ -front服务日志:webase-front/log/ -``` - -## 7、附录 - -### 7.1 Java环境部署 - -此处给出简单步骤,供快速查阅。更详细的步骤,请参考[官网](http://www.oracle.com/technetwork/java/javase/downloads/index.html)。 - -(1)从[官网](http://www.oracle.com/technetwork/java/javase/downloads/index.html)下载对应版本的java安装包,并解压到相应目录 - -```shell -mkdir /software -tar -zxvf jdkXXX.tar.gz /software/ -``` - -(2)配置环境变量 - -```shell -export JAVA_HOME=/software/jdk1.8.0_121 -export PATH=$JAVA_HOME/bin:$PATH -export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar -``` - -### 7.2 Python部署 - -```shell -pip install requests 或 sudo yum install -y requests -``` - -### 7.3 MySQL-python部署 - -- CentOS - - ``` - sudo yum install -y MySQL-python - ``` - -- Ubuntu - - ``` - sudo apt-get install -y python-pip - sudo pip install MySQL-python - ``` - -### 7.4 数据库部署 - -此处以Centos/Fedora为例。 - -(1)切换到root - -```shell -sudo -s -``` - -(2)安装mysql - -```shell -yum install mysql* -#某些版本的linux,需要安装mariadb,mariadb是mysql的一个分支 -yum install mariadb* -``` - -(3)启动mysql - -```shell -service mysqld start -#若安装了mariadb,则使用下面的命令启动 -systemctl start mariadb.service -``` - -(4)初始化数据库用户 - -初次登录 - -```shell -mysql -u root -``` - -给root设置密码和授权远程访问 - -```sql -mysql > SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123456'); -mysql > GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION; -``` - -**安全温馨提示:** - -1. 例子中给出的数据库密码(123456)仅为样例,强烈建议设置成复杂密码 -2. 例子中的远程授权设置会使数据库在所有网络上都可以访问,请按具体的网络拓扑和权限控制情况,设置网络和权限帐号 - -授权test用户本地访问数据库 - -```sql -mysql > create user 'test'@'localhost' identified by '123456'; -``` - -(5)测试连接 - -另开一个ssh测试本地用户test是否可以登录数据库 - -```shell -mysql -utest -p123456 -h 127.0.0.1 -P 3306 -``` - -登陆成功后,执行以下sql语句,若出现错误,则用户授权不成功 - -```sql -mysql > show databases; -mysql > use test; -``` - -(6)创建数据库 - -登录数据库 - -```shell -mysql -utest -p123456 -h 127.0.0.1 -P 3306 -``` - -创建数据库 - -```sql -mysql > create database db_mgr; -``` - -## 8、常见问题 - -### 8.1 数据库安装后登录报错 - -腾讯云centos mysql安装完成后,登录报错:Access denied for user 'root'@'localhost' - -① 编辑 /etc/my.cnf ,在[mysqld] 部分最后添加一行 - -``` -skip-grant-tables -``` - -② 保存后重启mysql - -```shell -service mysqld restart -``` - -③ 输入以下命令,回车后输入密码再回车登录mysql - -``` -mysql -uroot -p mysql -``` - -### 8.2 找不到MySQLdb - -``` -Traceback (most recent call last): -... -ImportError: No module named MySQLdb -``` - -答:MySQL-python安装请参看部署 [附录7.3](#73-MySQL-python部署) - -### 8.3 部署时编译包下载慢 - -``` -... -Connecting to github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.216.112.19|:443... connected. -HTTP request sent, awaiting response... 200 OK -Length: 22793550 (22M) [application/octet-stream] -Saving to: ‘webase-front.zip’ - - 0% [ ] 77,974 37.8KB/s -``` - -答:部署过程会下载工程编译包,可能会因为网络原因导致过慢。此时,可以先手动下载( [webase-web](https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-web.zip) 、[webase-node-mgr](https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-node-mgr.zip) 、[webase-front](https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-front.zip)),再上传至服务器webase-deploy目录,在部署过程中根据提示不再重新下载编译包。 - -### 8.4 部署时数据库访问报错 - -``` -... -checking database connection -Traceback (most recent call last): - File "/data/temp/webase-deploy/comm/mysql.py", line 21, in dbConnect - conn = mdb.connect(host=mysql_ip, port=mysql_port, user=mysql_user, passwd=mysql_password, charset='utf8') - File "/usr/lib64/python2.7/site-packages/MySQLdb/__init__.py", line 81, in Connect - return Connection(*args, **kwargs) - File "/usr/lib64/python2.7/site-packages/MySQLdb/connections.py", line 193, in __init__ - super(Connection, self).__init__(*args, **kwargs2) -OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: YES)") -``` - -答:确认数据库用户名和密码 +## 社区 +联系我们:webase@webank.com \ No newline at end of file From 30b70d4742047e6cb82eb1a35ee84467c330ac1f Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Wed, 26 Jun 2019 11:09:59 +0800 Subject: [PATCH 045/119] Update install.md --- deploy/install.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/install.md b/deploy/install.md index 89bb564..f4c48eb 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -1,6 +1,6 @@ -# 一键部署说明 +# 快速搭建 ## 简介 -一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) +快速搭建可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) ## 贡献说明 请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 @@ -8,4 +8,4 @@ 希望在您的参与下,WeBASE会越来越好! ## 社区 -联系我们:webase@webank.com \ No newline at end of file +联系我们:webase@webank.com From b679c94f07d140a36e983b9bca4afe805d9a1d74 Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Wed, 26 Jun 2019 11:10:26 +0800 Subject: [PATCH 046/119] Update install.md --- deploy/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/install.md b/deploy/install.md index f4c48eb..7fe6b37 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -1,6 +1,6 @@ # 快速搭建 ## 简介 -快速搭建可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) +快速搭建可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 详细介绍请查看[快速搭建在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) ## 贡献说明 请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 From 2bc50e6d8473d9188e8478d4d777190eb9e8e444 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 28 Jun 2019 16:29:37 +0800 Subject: [PATCH 047/119] modify depend --- deploy/comm/check.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 33bfa76..28db8ea 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -117,17 +117,16 @@ def hasInstallServer(server): def installByYum(server): if isCentos(): - result = doCmd("sudo yum -y install {}".format(server)) - if result["status"] !=0: - os.system("yum install epel-release") - os.system("sudo yum install python-pip") - os.system("pip install --upgrade pip") + result = doCmdIgnoreException("sudo yum -y install {}".format(server)) + if result["status"] != 0: + os.system("sudo yum -y install epel-release") + os.system("sudo yum -y install python-pip") os.system("pip install requests") - result = doCmd("yum install {}".format(server)) + result = doCmd("sudo yum -y install {}".format(server)) elif isSuse(): - os.system("sudo zypper install -y {}".format(server)) + os.system("sudo zypper install -y {}".format(server)) elif isUbuntu(): - os.system("sudo apt-get install -y {}".format(server)) + os.system("sudo apt-get install -y {}".format(server)) else: raise Exception("error,not support this platform,only support centos,suse,ubuntu.") return From a0f943548ae5809c20c968bebfdab8ff2ca49abc Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Sun, 7 Jul 2019 19:13:27 +0800 Subject: [PATCH 048/119] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc5231b..8c768d1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需 ## 贡献说明 -请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 +请阅读我们的[贡献文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/CONTRIBUTING.html),了解如何贡献代码,并提交你的贡献。 希望在您的参与下,WeBASE会越来越好! From 94a0d6d1d11c85be8185086ac28a74cbbc6229bd Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 9 Jul 2019 20:25:15 +0800 Subject: [PATCH 049/119] support existed chain --- deploy/build_chain.sh | 348 ++++++++++++++++++++------------------- deploy/comm/build.py | 193 +++++++++++++--------- deploy/comm/check.py | 47 +++++- deploy/comm/utils.py | 12 ++ deploy/common.properties | 20 ++- deploy/deploy.py | 10 +- deploy/install.md | 2 +- 7 files changed, 367 insertions(+), 265 deletions(-) diff --git a/deploy/build_chain.sh b/deploy/build_chain.sh index eb51ae3..6972f1c 100644 --- a/deploy/build_chain.sh +++ b/deploy/build_chain.sh @@ -2,24 +2,25 @@ set -e +# default value ca_file= #CA key node_num=1 ip_file= -agency_array= -group_array= ip_param= use_ip_param= +agency_array= +group_array= ip_array= output_dir=nodes port_start=(30300 20200 8545) state_type=storage -storage_type=LevelDB +storage_type=rocksdb conf_path="conf" bin_path= make_tar= debug_log="false" log_level="info" -logfile=build.log +logfile=${PWD}/build.log listen_ip="127.0.0.1" bcos_bin_name=fisco-bcos guomi_mode= @@ -28,12 +29,15 @@ gm_conf_path="gmconf/" current_dir=$(pwd) consensus_type="pbft" TASSL_CMD="${HOME}"/.tassl +enable_parallel=true auto_flush="true" # trans timestamp from seconds to milliseconds timestamp=$(($(date '+%s')*1000)) chain_id=1 -fisco_version="" -OS= +compatibility_version="" +default_version="2.0.0" +macOS="" +download_timeout=360 help() { echo $1 @@ -45,11 +49,11 @@ Usage: -o Default ./nodes/ -p Default 30300,20200,8545 means p2p_port start from 30300, channel_port from 20200, jsonrpc_port from 8545 -i Default 127.0.0.1. If set -i, listen 0.0.0.0 - -v Default get version from FISCO-BCOS/blob/master/release_note.txt. eg. 2.0.0-rc1 + -v Default get version from https://github.com/FISCO-BCOS/FISCO-BCOS/releases. If set use specificd version binary + -s Default rocksdb. Options can be rocksdb / mysql / external, rocksdb is recommended -d Default off. If set -d, build with docker - -s Default storage. if set -s, use mpt - -S Default leveldb. if set -S, use external -c Default PBFT. If set -c, use Raft + -m Default storageState. if set -m, use mpt state -C Default 1. Can set uint. -g Default no -z Default no @@ -76,9 +80,19 @@ LOG_INFO() echo -e "\033[32m[INFO] ${content}\033[0m" } +exit_with_clean() +{ + local content=${1} + echo -e "\033[31m[ERROR] ${content}\033[0m" + if [ -d "${output_dir}" ];then + rm -rf ${output_dir} + fi + exit 1 +} + parse_params() { -while getopts "f:l:o:p:e:t:v:icszhgTFdC:S" option;do +while getopts "f:l:o:p:e:t:v:s:C:iczhgmTFd" option;do case $option in f) ip_file=$OPTARG use_ip_param="false" @@ -88,20 +102,25 @@ while getopts "f:l:o:p:e:t:v:icszhgTFdC:S" option;do ;; o) output_dir=$OPTARG;; i) listen_ip="0.0.0.0";; - v) fisco_version="$OPTARG";; + v) compatibility_version="$OPTARG";; p) port_start=(${OPTARG//,/ }) if [ ${#port_start[@]} -ne 3 ];then LOG_WARN "start port error. e.g: 30300,20200,8545" && exit 1;fi ;; e) bin_path=$OPTARG;; - s) state_type=mpt;; - S) storage_type="external";; + m) state_type=mpt;; + s) storage_type=$OPTARG + if [ -z "${storage_type}" ];then + LOG_WARN "${storage_type} is not supported storage." + exit 1; + fi + ;; t) CertConfig=$OPTARG;; c) consensus_type="raft";; C) chain_id=$OPTARG - if [ -z $(grep '^[[:digit:]]*$' <<< "${chain_id}") ];then - LOG_WARN "${chain_id} is not a positive integer." - exit 1; - fi + if [ -z $(grep '^[[:digit:]]*$' <<< "${chain_id}") ];then + LOG_WARN "${chain_id} is not a positive integer." + exit 1; + fi ;; T) debug_log="true" log_level="debug" @@ -109,7 +128,8 @@ while getopts "f:l:o:p:e:t:v:icszhgTFdC:S" option;do F) auto_flush="false";; z) make_tar="yes";; g) guomi_mode="yes";; - d) docker_mode="yes";; + d) docker_mode="yes" + [ ! -z "${macOS}" ] && LOG_WARN "Docker desktop of macOS can't support docker mode of FISCO BCOS!" && exit 1;; h) help;; esac done @@ -129,7 +149,6 @@ LOG_INFO "Start Port : ${port_start[*]}" LOG_INFO "Server IP : ${ip_array[*]}" LOG_INFO "State Type : ${state_type}" LOG_INFO "RPC listen IP : ${listen_ip}" -[ ! -z ${pkcs12_passwd} ] && LOG_INFO "SDK PKCS12 Passwd : ${pkcs12_passwd}" LOG_INFO "Output Dir : ${output_dir}" LOG_INFO "CA Key Path : $ca_file" [ ! -z $guomi_mode ] && LOG_INFO "Guomi mode : $guomi_mode" @@ -137,28 +156,18 @@ echo "================================================================" LOG_INFO "All completed. Files in ${output_dir}" } -fail_message() -{ - echo $1 - false -} - -EXIT_CODE=-1 - check_env() { [ ! -z "$(openssl version | grep 1.0.2)" ] || [ ! -z "$(openssl version | grep 1.1)" ] || [ ! -z "$(openssl version | grep reSSL)" ] || { echo "please install openssl!" #echo "download openssl from https://www.openssl.org." echo "use \"openssl version\" command to check." - exit $EXIT_CODE + exit 1 } if [ ! -z "$(openssl version | grep reSSL)" ];then export PATH="/usr/local/opt/openssl/bin:$PATH" fi if [ "$(uname)" == "Darwin" ];then - OS="macOS" - elif [ "$(uname -s)" == " Linux " ];then - OS="Linux" + macOS="macOS" fi } @@ -191,34 +200,31 @@ check_name() { local name="$1" local value="$2" [[ "$value" =~ ^[a-zA-Z0-9._-]+$ ]] || { - echo "$name name [$value] invalid, it should match regex: ^[a-zA-Z0-9._-]+\$" - exit $EXIT_CODE + exit_with_clean "$name name [$value] invalid, it should match regex: ^[a-zA-Z0-9._-]+\$" } } file_must_exists() { if [ ! -f "$1" ]; then - echo "$1 file does not exist, please check!" - exit $EXIT_CODE + exit_with_clean "$1 file does not exist, please check!" fi } dir_must_exists() { if [ ! -d "$1" ]; then - echo "$1 DIR does not exist, please check!" - exit $EXIT_CODE + exit_with_clean "$1 DIR does not exist, please check!" fi } dir_must_not_exists() { if [ -e "$1" ]; then - echo "$1 DIR exists, please clean old DIR!" - exit $EXIT_CODE + LOG_WARN "$1 DIR exists, please clean old DIR!" + exit 1 fi } gen_chain_cert() { - path="$2" + local path="${1}" name=$(getname "$path") echo "$path --- $name" dir_must_not_exists "$path" @@ -232,8 +238,8 @@ gen_chain_cert() { } gen_agency_cert() { - chain="$2" - agencypath="$3" + local chain="${1}" + local agencypath="${2}" name=$(getname "$agencypath") dir_must_exists "$chain" @@ -249,8 +255,6 @@ gen_agency_cert() { -in $agencydir/agency.csr -out $agencydir/agency.crt -extensions v4_req -extfile $chain/cert.cnf cp $chain/ca.crt $chain/cert.cnf $agencydir/ - cp $chain/ca.crt $agencydir/ca-agency.crt - more $agencydir/agency.crt | cat >>$agencydir/ca-agency.crt rm -f $agencydir/agency.csr echo "build $name agency cert successful!" @@ -273,13 +277,12 @@ gen_cert_secp256k1() { gen_node_cert() { if [ "" == "$(openssl ecparam -list_curves 2>&1 | grep secp256k1)" ]; then - echo "openssl don't support secp256k1, please upgrade openssl!" - exit $EXIT_CODE + exit_with_clean "openssl don't support secp256k1, please upgrade openssl!" fi - agpath="$2" + agpath="${1}" agency=$(getname "$agpath") - ndpath="$3" + ndpath="${2}" node=$(getname "$ndpath") dir_must_exists "$agpath" file_must_exists "$agpath/agency.key" @@ -312,7 +315,7 @@ EOF } gen_chain_cert_gm() { - path="$2" + local path="${1}" name=$(getname "$path") echo "$path --- $name" dir_must_not_exists "$path" @@ -325,8 +328,6 @@ gen_chain_cert_gm() { $TASSL_CMD genpkey -paramfile gmsm2.param -out $chaindir/gmca.key $TASSL_CMD req -config gmcert.cnf -x509 -days 3650 -subj "/CN=$name/O=fiscobcos/OU=chain" -key $chaindir/gmca.key -extensions v3_ca -out $chaindir/gmca.crt - ls $chaindir - cp gmcert.cnf gmsm2.param $chaindir if $(cp gmcert.cnf gmsm2.param $chaindir) @@ -338,8 +339,8 @@ gen_chain_cert_gm() { } gen_agency_cert_gm() { - chain="$2" - agencypath="$3" + local chain="${1}" + local agencypath="${2}" name=$(getname "$agencypath") dir_must_exists "$chain" @@ -354,8 +355,6 @@ gen_agency_cert_gm() { $TASSL_CMD x509 -req -CA $chain/gmca.crt -CAkey $chain/gmca.key -days 3650 -CAcreateserial -in $agencydir/gmagency.csr -out $agencydir/gmagency.crt -extfile $chain/gmcert.cnf -extensions v3_agency_root cp $chain/gmca.crt $chain/gmcert.cnf $chain/gmsm2.param $agencydir/ - cp $chain/gmca.crt $agencydir/ca-agency.crt - more $agencydir/gmagency.crt | cat >>$agencydir/ca-agency.crt rm -f $agencydir/gmagency.csr echo "build $name agency cert successful!" @@ -377,13 +376,12 @@ gen_node_cert_with_extensions_gm() { gen_node_cert_gm() { if [ "" = "$(openssl ecparam -list_curves 2>&1 | grep secp256k1)" ]; then - echo "openssl don't support secp256k1, please upgrade openssl!" - exit $EXIT_CODE + exit_with_clean "openssl don't support secp256k1, please upgrade openssl!" fi - agpath="$2" + agpath="${1}" agency=$(getname "$agpath") - ndpath="$3" + ndpath="${2}" node=$(getname "$ndpath") dir_must_exists "$agpath" file_must_exists "$agpath/gmagency.key" @@ -427,34 +425,24 @@ generate_config_ini() fi cat << EOF > ${output} [rpc] - ; rpc listen ip listen_ip=${listen_ip} - ; channelserver listen port channel_listen_port=$(( offset + port_start[1] )) - ; jsonrpc listen port jsonrpc_listen_port=$(( offset + port_start[2] )) [p2p] - ; p2p listen ip listen_ip=0.0.0.0 - ; p2p listen port listen_port=$(( offset + port_start[0] )) + ;enable_compress=true ; nodes to connect $ip_list - ;enable/disable network compress - ;enable_compress=false -;certificate rejected list [certificate_blacklist] ; crl.0 should be nodeid, nodeid's length is 128 ;crl.0= -;group configurations -;WARNING: group 0 is forbided [group] group_data_path=data/ group_config_path=${conf_path}/ -;certificate configuration [network_security] ; directory the certificates located in data_path=${conf_path}/ @@ -465,32 +453,27 @@ generate_config_ini() ; the ca certificate file ca_cert=${prefix}ca.crt -; storage security releated configurations [storage_security] -; enable storage_security or not -;enable=true -; the IP of key mananger -;key_manager_ip= -; the Port of key manager -;key_manager_port= -;cipher_data_key= + enable=false + ; the IP of key mananger + key_manager_ip= + ; the Port of key manager + key_manager_port= + cipher_data_key= [chain] id=${chain_id} [compatibility] - supported_version=${fisco_version} -;log configurations + ; supported_version should nerver be changed + supported_version=${compatibility_version} [log] - ; the directory of the log + enable=true log_path=./log ; info debug trace level=${log_level} ; MB max_log_file_size=200 - ; control log auto_flush flush=${auto_flush} - ; easylog config - format=%level|%datetime{%Y-%M-%d %H:%m:%s:%g}|%msg log_flush_threshold=100 EOF } @@ -501,24 +484,18 @@ generate_group_genesis() local index=$2 local node_list=$3 cat << EOF > ${output} -;consensus configuration [consensus] - ;consensus algorithm type, now support PBFT(consensus_type=pbft) and Raft(consensus_type=raft) + ; consensus algorithm type, now support PBFT(consensus_type=pbft) and Raft(consensus_type=raft) consensus_type=${consensus_type} - ;the max number of transactions of a block + ; the max number of transactions of a block max_trans_num=1000 - ;the node id of leaders + ; the node id of consensusers ${node_list} -[storage] - ;storage db type, leveldb or external - type=${storage_type} - topic=DB [state] - ;support mpt/storage + ; support mpt/storage type=${state_type} - -;tx gas limit [tx] + ; transaction gas limit gas_limit=300000000 [group] id=${index} @@ -530,18 +507,31 @@ function generate_group_ini() { local output="${1}" cat << EOF > ${output} -; the ttl for broadcasting pbft message [consensus] + ; the ttl for broadcasting pbft message ;ttl=2 - ;min block generation time(ms), the max block generation time is 1000 ms + ; min block generation time(ms), the max block generation time is 1000 ms ;min_block_generation_time=500 ;enable_dynamic_block_size=true - -;txpool limit +[storage] + ; storage db type, rocksdb / mysql / external, rocksdb is recommended + type=${storage_type} + ; max cache memeory, MB + max_capacity=256 + max_forward_block=10 + ; only for external + max_retry=100 + topic=DB + ; only for mysql + db_ip=127.0.0.1 + db_port=3306 + db_username= + db_passwd= + db_name= [tx_pool] limit=150000 [tx_execute] - enable_parallel=true + enable_parallel=${enable_parallel} EOF } @@ -720,19 +710,21 @@ EOF generate_node_scripts() { local output=$1 - local docker_tag="latest" + local docker_tag="v${compatibility_version}" generate_script_template "$output/start.sh" - local ps_cmd="\`ps aux|grep \${fisco_bcos}|grep -v grep|awk '{print \$2}'\`" - local start_cmd="nohup \${fisco_bcos} -c config.ini 2>>nohup.out" + local ps_cmd="\$(ps aux|grep \${fisco_bcos}|grep -v grep|awk '{print \$2}')" + local start_cmd="nohup \${fisco_bcos} -c config.ini >>nohup.out 2>&1" local stop_cmd="kill \${node_pid}" local pid="pid" - local log_cmd="cat nohup.out" + local log_cmd="tail -n20 nohup.out" + local check_success="\$(${log_cmd} | grep running)" if [ ! -z ${docker_mode} ];then - ps_cmd="\`docker ps |grep \${SHELL_FOLDER//\//} | grep -v grep|awk '{print \$1}'\`" - start_cmd="docker run -d --rm --name \${SHELL_FOLDER//\//} -v \${SHELL_FOLDER}:/data --network=host -w=/data fiscoorg/fiscobcos:${docker_tag} -c config.ini >>nohup.out" + ps_cmd="\$(docker ps |grep \${SHELL_FOLDER//\//} | grep -v grep|awk '{print \$1}')" + start_cmd="docker run -d --rm --name \${SHELL_FOLDER//\//} -v \${SHELL_FOLDER}:/data --network=host -w=/data fiscoorg/fiscobcos:${docker_tag} -c config.ini" stop_cmd="docker kill \${node_pid} 2>/dev/null" pid="container id" - log_cmd="docker logs \${SHELL_FOLDER//\//}" + log_cmd="tail -n20 \$(docker inspect --format='{{.LogPath}}' \${SHELL_FOLDER//\//})" + check_success="success" fi cat << EOF >> "$output/start.sh" fisco_bcos=\${SHELL_FOLDER}/../${bcos_bin_name} @@ -751,7 +743,8 @@ i=0 while [ \$i -lt \${try_times} ] do node_pid=${ps_cmd} - if [ ! -z \${node_pid} ];then + success_flag=${check_success} + if [[ ! -z \${node_pid} && ! -z "\${success_flag}" ]];then echo -e "\033[32m \${node} start successfully\033[0m" exit 0 fi @@ -767,15 +760,15 @@ EOF fisco_bcos=\${SHELL_FOLDER}/../${bcos_bin_name} node=\$(basename \${SHELL_FOLDER}) node_pid=${ps_cmd} -try_times=5 +try_times=10 i=0 +if [ -z \${node_pid} ];then + echo " \${node} isn't running." + exit 0 +fi +[ ! -z \${node_pid} ] && ${stop_cmd} > /dev/null while [ \$i -lt \${try_times} ] do - if [ -z \${node_pid} ];then - echo " \${node} isn't running." - exit 0 - fi - [ ! -z \${node_pid} ] && ${stop_cmd} > /dev/null sleep 0.6 node_pid=${ps_cmd} if [ -z \${node_pid} ];then @@ -863,25 +856,27 @@ generate_server_scripts() # echo "ip_array=(\$(ifconfig | grep inet | grep -v inet6 | awk '{print \$2}'))" >> "$output/start_all.sh" # echo "if echo \${ip_array[@]} | grep -w \"${ip}\" &>/dev/null; then echo \"start node_${ip}_${i}\" && bash \${SHELL_FOLDER}/node_${ip}_${i}/start.sh; fi" >> "${output_dir}/start_all.sh" cat << EOF >> "$output/start_all.sh" -for directory in \`ls \${SHELL_FOLDER}\` +dirs=(\$(ls -l \${SHELL_FOLDER} | awk '/^d/ {print \$NF}')) +for directory in \${dirs[*]} do - if [[ -d "\${SHELL_FOLDER}/\${directory}" && -f "\${SHELL_FOLDER}/\${directory}/start.sh" ]];then + if [[ -f "\${SHELL_FOLDER}/\${directory}/config.ini" && -f "\${SHELL_FOLDER}/\${directory}/start.sh" ]];then echo "try to start \${directory}" bash \${SHELL_FOLDER}/\${directory}/start.sh & fi done -sleep 3.5 +wait EOF generate_script_template "$output/stop_all.sh" cat << EOF >> "$output/stop_all.sh" -for directory in \`ls \${SHELL_FOLDER}\` +dirs=(\$(ls -l \${SHELL_FOLDER} | awk '/^d/ {print \$NF}')) +for directory in \${dirs[*]} do if [[ -d "\${SHELL_FOLDER}/\${directory}" && -f "\${SHELL_FOLDER}/\${directory}/stop.sh" ]];then echo "try to stop \${directory}" bash \${SHELL_FOLDER}/\${directory}/stop.sh & fi done -sleep 3 +wait EOF } @@ -894,13 +889,51 @@ parse_ip_config() agency_array[n]=$(echo ${line} | awk '{print $2}') group_array[n]=$(echo ${line} | awk '{print $3}') if [ -z "${ip_array[$n]}" -o -z "${agency_array[$n]}" -o -z "${group_array[$n]}" ];then - LOG_WARN "Please check ${config}, make sure there is no empty line!" - return 1 + exit_with_clean "Please check ${config}, make sure there is no empty line!" fi ((++n)) done < ${config} } +download_bin() +{ + bin_path=${output_dir}/${bcos_bin_name} + package_name="fisco-bcos.tar.gz" + [ ! -z "${macOS}" ] && package_name="fisco-bcos-macOS.tar.gz" + [ ! -z "$guomi_mode" ] && package_name="fisco-bcos-gm.tar.gz" + if [[ ! -z "$guomi_mode" && ! -z ${macOS} ]];then + exit_with_clean "We don't provide binary of GuoMi on macOS. Please compile source code and use -e option to specific fisco-bcos binary path" + fi + Download_Link="https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v${compatibility_version}/${package_name}" + LOG_INFO "Downloading fisco-bcos binary from ${Download_Link} ..." + if [ $(curl -IL -o /dev/null -s -w %{http_code} https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}) == 200 ];then + curl -LO ${Download_Link} --speed-time 30 --speed-limit 1024 -m ${download_timeout} || { + LOG_INFO "Download speed is too low, try https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}" + curl -LO https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name} + } + else + curl -LO ${Download_Link} + fi + tar -zxf ${package_name} && mv fisco-bcos ${bin_path} && rm ${package_name} + chmod a+x ${bin_path} +} + +check_bin() +{ + echo "Checking fisco-bcos binary..." + bin_version=$(${bin_path} -v) + if [ -z "$(echo ${bin_version} | grep 'FISCO-BCOS')" ];then + exit_with_clean "${bin_path} is wrong. Please correct it and try again." + fi + if [[ ! -z ${guomi_mode} && -z $(echo ${bin_version} | grep 'gm') ]];then + exit_with_clean "${bin_path} isn't gm version. Please correct it and try again." + fi + if [[ -z ${guomi_mode} && ! -z $(echo ${bin_version} | grep 'gm') ]];then + exit_with_clean "${bin_path} isn't standard version. Please correct it and try again." + fi + echo "Binary check passed." +} + main() { output_dir="$(pwd)/${output_dir}" @@ -909,8 +942,7 @@ if [ "${use_ip_param}" == "true" ];then ip_array=(${ip_param//,/ }) elif [ "${use_ip_param}" == "false" ];then if ! parse_ip_config $ip_file ;then - echo "Parse $ip_file error!" - exit 1 + exit_with_clean "Parse $ip_file error!" fi else help @@ -920,41 +952,20 @@ fi dir_must_not_exists ${output_dir} mkdir -p "${output_dir}" -# get fisco_version -if [ -z "${fisco_version}" ];then - fisco_version=$(curl -s https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/release_note.txt | sed "s/^[vV]//") +if [ -z "${compatibility_version}" ];then + compatibility_version=$(curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "tag_name" | grep "\"v2\.[0-9]\.[0-9]\"" | sort -u | tail -n 1 | cut -d \" -f 4 | sed "s/^[vV]//") +fi +# in case network is broken +if [ -z "${compatibility_version}" ];then + compatibility_version="${default_version}" fi # download fisco-bcos and check it if [ -z ${docker_mode} ];then - if [[ -z ${bin_path} && -z ${OS} ]];then - bin_path=${output_dir}/${bcos_bin_name} - package_name="fisco-bcos.tar.gz" - [ ! -z "$guomi_mode" ] && package_name="fisco-bcos-gm.tar.gz" - Download_Link="https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v${fisco_version}/${package_name}" - LOG_INFO "Downloading fisco-bcos binary from ${Download_Link} ..." - curl -LO ${Download_Link} - tar -zxf ${package_name} && mv fisco-bcos ${bin_path} && rm ${package_name} - chmod a+x ${bin_path} - elif [[ -z ${bin_path} && ! -z ${OS} ]];then - echo "Please use docker mode to run fisco-bcos on macOS Or compile source code and use -e option to specific fisco-bcos binary path" - exit 1 + if [[ -z ${bin_path} ]];then + download_bin else - echo "Checking fisco-bcos binary..." - bin_version=$(${bin_path} -v) - if [ -z "$(echo ${bin_version} | grep 'FISCO-BCOS')" ];then - LOG_WARN "${bin_path} is wrong. Please correct it and try again." - exit 1 - fi - if [[ ! -z ${guomi_mode} && -z $(echo ${bin_version} | grep 'gm') ]];then - LOG_WARN "${bin_path} isn't gm version. Please correct it and try again." - exit 1 - fi - if [[ -z ${guomi_mode} && ! -z $(echo ${bin_version} | grep 'gm') ]];then - LOG_WARN "${bin_path} isn't standard version. Please correct it and try again." - exit 1 - fi - echo "Binary check passed." + check_bin fi fi if [ -z ${CertConfig} ] || [ ! -e ${CertConfig} ];then @@ -976,16 +987,16 @@ echo "==============================================================" if [ ! -e "$ca_file" ]; then echo "Generating CA key..." dir_must_not_exists ${output_dir}/chain - gen_chain_cert "" ${output_dir}/chain >${output_dir}/${logfile} 2>&1 || fail_message "openssl error!" + gen_chain_cert ${output_dir}/chain >${logfile} 2>&1 || exit_with_clean "openssl error!" mv ${output_dir}/chain ${output_dir}/cert if [ "${use_ip_param}" == "false" ];then for agency_name in ${agency_array[*]};do if [ ! -d ${output_dir}/cert/${agency_name} ];then - gen_agency_cert "" ${output_dir}/cert ${output_dir}/cert/${agency_name} >${output_dir}/${logfile} 2>&1 + gen_agency_cert ${output_dir}/cert ${output_dir}/cert/${agency_name} >${logfile} 2>&1 fi done else - gen_agency_cert "" ${output_dir}/cert ${output_dir}/cert/agency >${output_dir}/${logfile} 2>&1 + gen_agency_cert ${output_dir}/cert ${output_dir}/cert/agency >${logfile} 2>&1 fi ca_file="${output_dir}/cert/ca.key" fi @@ -997,9 +1008,9 @@ if [ -n "$guomi_mode" ]; then echo "Generating Guomi CA key..." dir_must_not_exists ${output_dir}/gmchain - gen_chain_cert_gm "" ${output_dir}/gmchain >${output_dir}/build.log 2>&1 || fail_message "openssl error!" #生成secp256k1算法的CA密钥 + gen_chain_cert_gm ${output_dir}/gmchain >${output_dir}/build.log 2>&1 || exit_with_clean "openssl error!" #生成secp256k1算法的CA密钥 mv ${output_dir}/gmchain ${output_dir}/gmcert - gen_agency_cert_gm "" ${output_dir}/gmcert ${output_dir}/gmcert/agency >${output_dir}/build.log 2>&1 + gen_agency_cert_gm ${output_dir}/gmcert ${output_dir}/gmcert/agency >${output_dir}/build.log 2>&1 ca_file="${output_dir}/gmcert/ca.key" fi @@ -1016,21 +1027,20 @@ groups_count= for line in ${ip_array[*]};do ip=${line%:*} num=${line#*:} - checkIP=$(echo $ip|grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") - if [ -z "${checkIP}" ];then - LOG_WARN "Please check IP address: ${ip}" + if [ -z $(echo $ip | grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") ];then + exit_with_clean "Please check IP address: ${ip}" fi [ "$num" == "$ip" ] || [ -z "${num}" ] && num=${node_num} echo "Processing IP:${ip} Total:${num} Agency:${agency_array[${server_count}]} Groups:${group_array[server_count]}" [ -z "${ip_node_counts[${ip//./}]}" ] && ip_node_counts[${ip//./}]=0 for ((i=0;i> ${output_dir}/${logfile} + echo "Processing IP:${ip} ID:${i} node's key" >> ${logfile} node_dir="${output_dir}/${ip}/node${ip_node_counts[${ip//./}]}" - [ -d "${node_dir}" ] && echo "${node_dir} exist! Please delete!" && exit 1 + [ -d "${node_dir}" ] && exit_with_clean "${node_dir} exist! Please delete!" while : do - gen_node_cert "" ${output_dir}/cert/${agency_array[${server_count}]} ${node_dir} >${output_dir}/${logfile} 2>&1 + gen_node_cert ${output_dir}/cert/${agency_array[${server_count}]} ${node_dir} >${logfile} 2>&1 mkdir -p ${conf_path}/ rm node.param node.private node.pubkey agency.crt mv *.* ${conf_path}/ @@ -1046,7 +1056,7 @@ for line in ${ip_array[*]};do fi if [ -n "$guomi_mode" ]; then - gen_node_cert_gm "" ${output_dir}/gmcert/agency ${node_dir} >${output_dir}/build.log 2>&1 + gen_node_cert_gm ${output_dir}/gmcert/agency ${node_dir} >${output_dir}/build.log 2>&1 mkdir -p ${gm_conf_path}/ mv ./*.* ${gm_conf_path}/ @@ -1090,7 +1100,7 @@ for line in ${ip_array[*]};do node_groups=(${group_array[server_count]//,/ }) for j in ${node_groups[@]};do if [ -z "${groups_count[${j}]}" ];then groups_count[${j}]=0;fi - echo "groups_count[${j}]=${groups_count[${j}]}" >> ${output_dir}/${logfile} + echo "groups_count[${j}]=${groups_count[${j}]}" >> ${logfile} groups[${j}]=$"${groups[${j}]}node.${groups_count[${j}]}=${nodeid} " ((++groups_count[j])) @@ -1107,7 +1117,7 @@ for line in ${ip_array[*]};do done sdk_path="${output_dir}/${ip}/sdk" if [ ! -d ${sdk_path} ];then - gen_node_cert "" ${output_dir}/cert/${agency_array[${server_count}]} "${sdk_path}">${output_dir}/${logfile} 2>&1 + gen_node_cert ${output_dir}/cert/${agency_array[${server_count}]} "${sdk_path}">${logfile} 2>&1 cat ${output_dir}/cert/${agency_array[${server_count}]}/agency.crt >> node.crt rm node.param node.private node.pubkey node.nodeid agency.crt cp ${output_dir}/cert/ca.crt ${sdk_path}/ @@ -1128,7 +1138,7 @@ for line in ${ip_array[*]};do [ -z "${ip_node_counts[${ip//./}]}" ] && ip_node_counts[${ip//./}]=0 echo "Processing IP:${ip} Total:${num} Agency:${agency_array[${server_count}]} Groups:${group_array[server_count]}" for ((i=0;i> ${output_dir}/${logfile} + echo "Processing IP:${ip} ID:${i} config files..." >> ${logfile} node_dir="${output_dir}/${ip}/node${ip_node_counts[${ip//./}]}" generate_config_ini "${node_dir}/config.ini" ${ip} "${group_array[server_count]}" if [ "${use_ip_param}" == "false" ];then @@ -1149,7 +1159,7 @@ for line in ${ip_array[*]};do if [ -n "$make_tar" ];then cd ${output_dir} && tar zcf "${ip}.tar.gz" "${ip}" && cd ${current_dir};fi ((++server_count)) done -rm ${output_dir}/${logfile} +rm ${logfile} if [ "${use_ip_param}" == "false" ];then echo "==============================================================" for l in $(seq 0 ${#groups_count[@]});do diff --git a/deploy/comm/build.py b/deploy/comm/build.py index efd3273..4cd8682 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -11,10 +11,10 @@ def do(): print "===================== deploy start... =====================" - startNode() - startWeb() - startMgr() - startFront() + installNode() + installWeb() + installMgr() + installFront() print "===================== deploy end... =====================" print "===================== version V1.0.0 =====================" return @@ -26,56 +26,61 @@ def end(): stopFront() return -def startNode(): - print "============== node start... ==============" - fisco_version = getCommProperties("fisco.version") - nodes = getCommProperties("node.counts") +def installNode(): + if_exist_fisco = getCommProperties("if.exist.fisco") node_p2pPort = int(getCommProperties("node.p2pPort")) node_channelPort = int(getCommProperties("node.channelPort")) node_rpcPort = int(getCommProperties("node.rpcPort")) + fisco_version = getCommProperties("fisco.version") + node_counts = getCommProperties("node.counts") - # init configure file - if not os.path.exists(currentDir + "/nodetemp"): - doCmd('cp -f nodeconf nodetemp') - else: - doCmd('cp -f nodetemp nodeconf') - - node_counts = 2 - if nodes is not "nodeCounts": - node_counts = int(nodes) - doCmd('sed -i "s/nodeCounts/{}/g" nodeconf'.format(node_counts)) - - if not os.path.exists("{}/nodes".format(currentDir)): - doCmdIgnoreException("chmod u+x *.sh") - doCmdIgnoreException("dos2unix *.sh") - result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - if result_build["status"] == 0: - if_build = 'completed' in result_build["output"] - if not if_build: - print "======= node build fail! =======" - sys.exit(0) + if if_exist_fisco == "no": + print "============== node install... ==============" + # init configure file + if not os.path.exists(currentDir + "/nodetemp"): + doCmd('cp -f nodeconf nodetemp') else: - print "======= node build fail! =======" - sys.exit(0) - else: - info = raw_input("节点目录nodes已经存在。是否重新安装?[y/n]:") - if info == "y" or info == "Y": - doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") - doCmd("rm -rf nodes") + doCmd('cp -f nodetemp nodeconf') + + node_nums = 2 + if node_counts != "nodeCounts": + node_nums = int(node_counts) + doCmd('sed -i "s/nodeCounts/{}/g" nodeconf'.format(node_nums)) + + if not os.path.exists("{}/nodes".format(currentDir)): doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - if result_build["status"] == 0: - if_build = 'completed' in result_build["output"] - if not if_build: - print "======= node build fail! =======" - sys.exit(0) - else: - print "======= node build fail! =======" - sys.exit(0) + os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + # result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + # if result_build["status"] == 0: + # if_build = 'completed' in result_build["output"] + # if not if_build: + # print "======= node build fail! =======" + # sys.exit(0) + # else: + # print "======= node build fail! =======" + # sys.exit(0) + else: + info = raw_input("节点目录nodes已经存在。是否重新安装?[y/n]:") + if info == "y" or info == "Y": + doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") + doCmd("rm -rf nodes") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + startNode() + +def startNode(): + print "============== node start... ==============" + if_exist_fisco = getCommProperties("if.exist.fisco") + fisco_dir = getCommProperties("fisco.dir") + if if_exist_fisco == "no": + fisco_dir = currentDir + "/nodes/127.0.0.1" - node_dir = currentDir + "/nodes/127.0.0.1" - os.chdir(node_dir) + if not os.path.exists(fisco_dir + "/start_all.sh"): + print "======= fisco dir:{} is not correct. please check! =======".format(fisco_dir) + sys.exit(0) + os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash start_all.sh") @@ -83,12 +88,15 @@ def startNode(): return def stopNode(): - if not os.path.exists("{}/nodes".format(currentDir)): - print "======= nodes is not exists! =======" - sys.exit(0) + if_exist_fisco = getCommProperties("if.exist.fisco") + fisco_dir = getCommProperties("fisco.dir") + if if_exist_fisco == "no": + fisco_dir = currentDir + "/nodes/127.0.0.1" - node_dir = currentDir + "/nodes/127.0.0.1" - os.chdir(node_dir) + if not os.path.exists(fisco_dir + "/stop_all.sh"): + print "======= fisco dir:{} is not correct. please check! =======".format(fisco_dir) + sys.exit(0) + os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash stop_all.sh") @@ -119,12 +127,25 @@ def changeWebConfig(): return -def startWeb(): - print "============== web start... ==============" +def installWeb(): + print "============== web install... ==============" os.chdir(currentDir) pullSourceExtract("web.package.url","webase-web") changeWebConfig() + startWeb() +def startWeb(): + print "============== web start... ==============" + if os.path.exists("/run/nginx-webase-web.pid"): + info = raw_input("web进程已经存在,是否kill进程强制重启?[y/n]:") + if info == "y" or info == "Y": + fin = open('/run/nginx-webase-web.pid', 'r') + pid = fin.read() + cmd = "sudo kill -QUIT {}".format(pid) + os.system(cmd) + doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") + else: + sys.exit(0) nginx_config_dir = currentDir + "/comm/nginx.conf" res = doCmd("which nginx") if res["status"] == 0: @@ -189,8 +210,8 @@ def changeMgrConfig(): return -def startMgr(): - print "============== mgr start... ==============" +def installMgr(): + print "============== mgr install... ==============" os.chdir(currentDir) pullSourceExtract("mgr.package.url","webase-node-mgr") changeMgrConfig() @@ -218,7 +239,12 @@ def startMgr(): else: print "======= script init fail! =======" sys.exit(0) + startMgr() + return +def startMgr(): + print "============== mgr start... ==============" + server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") @@ -227,7 +253,7 @@ def startMgr(): if result["status"] == 0: if_started = 'started' in result["output"] if if_started: - info = raw_input("mgr进程已经存在,是否kill进程强制安装?[y/n]:") + info = raw_input("mgr进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": doCmd("bash stop.sh") result_start = doCmd("bash start.sh") @@ -235,13 +261,14 @@ def startMgr(): if_success = 'Success' in result_start["output"] if if_success: print "======= mgr start success! =======" + print "============== mgr end... ==============" + return else: print "======= mgr start fail! =======" sys.exit(0) else: print "======= mgr start fail! =======" sys.exit(0) - return else: sys.exit(0) if_success = 'Success' in result["output"] @@ -280,7 +307,11 @@ def changeFrontConfig(): frontPort = getCommProperties("front.port") nodeChannelPort = getCommProperties("node.channelPort") frontDb = getCommProperties("front.h2.db") - monitorDisk = getCommProperties("monitorDisk") + + if_exist_fisco = getCommProperties("if.exist.fisco") + fisco_dir = getCommProperties("fisco.dir") + if if_exist_fisco == "no": + fisco_dir = currentDir + "/nodes/127.0.0.1" # init file server_dir = currentDir + "/webase-front/conf" @@ -297,31 +328,44 @@ def changeFrontConfig(): doCmd('sed -i "s/127.0.0.1:8080/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) doCmd('sed -i "s%front_db%{}%g" {}/application.yml'.format(frontDb, server_dir)) - doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(monitorDisk, server_dir)) + doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(fisco_dir, server_dir)) return -def startFront(): - print "============== front start... ==============" +def installFront(): + print "============== front install... ==============" os.chdir(currentDir) pullSourceExtract("front.package.url","webase-front") changeFrontConfig() # check front db - frontDb = getCommProperties("front.h2.db") - db_dir = currentDir+"/h2" - res_file = checkFileName(db_dir,frontDb) - if res_file: - info = raw_input("front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) - if info == "y" or info == "Y": - doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) + # frontDb = getCommProperties("front.h2.db") + # db_dir = currentDir+"/h2" + # res_file = checkFileName(db_dir,frontDb) + # if res_file: + # info = raw_input("front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + # if info == "y" or info == "Y": + # doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) - nodeDir = currentDir + "/nodes/127.0.0.1/sdk" + # copy node crt + if_exist_fisco = getCommProperties("if.exist.fisco") + fisco_dir = getCommProperties("fisco.dir") + if if_exist_fisco == "no": + fisco_dir = currentDir + "/nodes/127.0.0.1" + if not os.path.exists(fisco_dir + "/sdk"): + print "======= fisco dir:{} is not correct. please check! =======".format(fisco_dir) + sys.exit(0) server_dir = currentDir + "/webase-front" os.chdir(server_dir) - # copy crt - copyFiles(nodeDir, server_dir + "/conf") + copyFiles(fisco_dir + "/sdk", server_dir + "/conf") + startFront() + return + +def startFront(): + print "============== front start... ==============" + server_dir = currentDir + "/webase-front" + os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") @@ -329,21 +373,22 @@ def startFront(): if result["status"] == 0: if_started = 'started' in result["output"] if if_started: - info = raw_input("front进程已经存在,是否kill进程强制安装?[y/n]:") + info = raw_input("front进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": doCmd("bash stop.sh") - result_start = doCmd("sh start.sh") + result_start = doCmd("bash start.sh") if result_start["status"] == 0: if_success = 'Success' in result_start["output"] if if_success: print "======= front start success! =======" + print "============== front end... ==============" + return else: print "======= front start fail! =======" sys.exit(0) else: print "======= front start fail! =======" sys.exit(0) - return else: sys.exit(0) if_success = 'Success' in result["output"] diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 28db8ea..307cb7b 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -25,7 +25,7 @@ def installRequirements(): hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print "check finished Sucessfully." + print "check finished sucessfully." return def checkSoft(): @@ -34,15 +34,45 @@ def checkSoft(): if res1["status"] != 0: print " error! java is not install or configure!" sys.exit(0) - print "check finished Sucessfully." + print "check finished sucessfully." return - + def checkNodePort(): print "check node port..." + if_exist_fisco = getCommProperties("if.exist.fisco") + if if_exist_fisco == "yes": + checkExistedNodePort() + elif if_exist_fisco == "no": + checkNewNodePort() + else: + print " error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco) + sys.exit(0) + print "check finished sucessfully." + +def checkExistedNodePort(): + deploy_ip = "127.0.0.1" + node_rpcPort = int(getCommProperties("node.rpcPort")) + node_p2pPort = int(getCommProperties("node.p2pPort")) + node_channelPort = int(getCommProperties("node.channelPort")) + res_rpcPort = net_if_used_no_msg("127.0.0.1",node_rpcPort) + if not res_rpcPort: + print " error! rpc port {} is not alive. please check.".format(node_rpcPort) + sys.exit(0) + res_p2pPort = net_if_used_no_msg("127.0.0.1",node_p2pPort) + if not res_p2pPort: + print " error! p2p port {} is not alive. please check.".format(node_p2pPort) + sys.exit(0) + res_channelPort = net_if_used_no_msg("127.0.0.1",node_channelPort) + if not res_channelPort: + print " error! channel port {} is not alive. please check.".format(node_channelPort) + sys.exit(0) + return + +def checkNewNodePort(): deploy_ip = "127.0.0.1" nodes = getCommProperties("node.counts") node_counts = 2 - if nodes is not "nodeCounts": + if nodes != "nodeCounts": node_counts = int(nodes) node_rpcPort = int(getCommProperties("node.rpcPort")) node_p2pPort = int(getCommProperties("node.p2pPort")) @@ -57,7 +87,6 @@ def checkNodePort(): res_channelPort = net_if_used("127.0.0.1",node_channelPort+i) if res_channelPort: sys.exit(0) - print "check finished Sucessfully." return def checkWebPort(): @@ -67,7 +96,7 @@ def checkWebPort(): res_web = net_if_used(deploy_ip,web_port) if res_web: sys.exit(0) - print "check finished Sucessfully." + print "check finished sucessfully." return def checkMgrPort(): @@ -77,7 +106,7 @@ def checkMgrPort(): res_mgr = net_if_used(deploy_ip,mgr_port) if res_mgr: sys.exit(0) - print "check finished Sucessfully." + print "check finished sucessfully." return def checkFrontPort(): @@ -87,7 +116,7 @@ def checkFrontPort(): res_front = net_if_used(deploy_ip,front_port) if res_front: sys.exit(0) - print "check finished Sucessfully." + print "check finished sucessfully." return def checkDbConnect(): @@ -98,7 +127,7 @@ def checkDbConnect(): if not ifLink: print 'The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port) sys.exit(0) - print "check finished Sucessfully." + print "check finished sucessfully." return def checkSdkDir(): diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index 3fa0a85..535af8a 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -40,6 +40,18 @@ def net_if_used(ip,port): finally: s.close() +def net_if_used_no_msg(ip,port): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s.settimeout(0.5) + try: + result=s.connect_ex((ip, int(port))) + if result==0: + return True + else: + return False + finally: + s.close() + def isUbuntu(): return platformStr.lower().find("ubuntu") > -1 diff --git a/deploy/common.properties b/deploy/common.properties index 359fffc..0014082 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,22 +1,32 @@ -[common] +[common] +# 编译包url,不用修改 web.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-web.zip mgr.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-node-mgr.zip front.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-front.zip +# 数据库信息 mysql.ip=127.0.0.1 mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword mysql.database=db_mgr +# 各个服务的端口 web.port=8080 mgr.port=8081 front.port=8082 -fisco.version=2.0.0-rc2 -node.counts=nodeCounts +# 节点端口信息 node.p2pPort=30300 node.channelPort=20200 node.rpcPort=8545 -front.h2.db=db_front -monitorDisk=/data \ No newline at end of file + +# 是否使用已有的链(yes/no) +if.exist.fisco=yes + +# 使用已有链时需配置(已有链的路径,start_all.sh脚本所在路径) +fisco.dir=fiscoDir + +# 搭建新链时需配置 +fisco.version=2.0.0-rc3 +node.counts=nodeCounts \ No newline at end of file diff --git a/deploy/deploy.py b/deploy/deploy.py index 74d2a98..3787cbc 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -8,28 +8,24 @@ def do(): help() return param = sys.argv[1] - if "startAll" == param: + if "installAll" == param: commCheck.do() commBuild.do() elif "stopAll" == param: commBuild.end() elif "startNode" == param: - commCheck.checkNodePort() commBuild.startNode() elif "stopNode" == param: commBuild.stopNode() elif "startWeb" == param: - commCheck.checkWebPort() commBuild.startWeb() elif "stopWeb" == param: commBuild.stopWeb() elif "startMgr" == param: - commCheck.checkMgrPort() commBuild.startMgr() elif "stopMgr" == param: commBuild.stopMgr() elif "startFront" == param: - commCheck.checkFrontPort() commBuild.startFront() elif "stopFront" == param: commBuild.stopFront() @@ -47,7 +43,7 @@ def help(): Parameter: check : check the environment - startAll : check the environment, deploy all server + installAll : check the environment, deploy all server stopAll : stop all server startNode : start nodes stopNode : stop nodes @@ -59,7 +55,7 @@ def help(): stopFront : stop front server Attention: - 1. Need to install python2.7, jdk1.8.0_121+, mysql 5.6+, MySQL-python first + 1. Need to install python2.7, jdk1.8, mysql 5.6, MySQL-python first 2. Need to ensure a smooth network 3. You need to install git, wget, nginx; if it is not installed, the installation script will automatically install these components, but this may fail. ''' diff --git a/deploy/install.md b/deploy/install.md index 89bb564..ac37c88 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -1,6 +1,6 @@ # 一键部署说明 ## 简介 -一键部署可以快速搭建WeBASE环境。包括节点(fisco-bcos)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。 详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) +一键部署可以快速搭建WeBASE管理台环境。包括节点(FISCO-BCOS 2.0)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。其中,节点的搭建是可选的,可以通过配置来选择使用已有链或者搭建新链。详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html)。 ## 贡献说明 请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 From cc076972e5b263e2850da7efbc190a5d539ce98c Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Thu, 11 Jul 2019 17:40:55 +0800 Subject: [PATCH 050/119] modify name --- deploy/build_chain.sh | 2 +- deploy/comm/build.py | 125 ++++++++++++++++++++------------------- deploy/comm/check.py | 41 ++++++++----- deploy/comm/mysql.py | 2 +- deploy/common.properties | 19 +++--- deploy/deploy.py | 32 +++++----- 6 files changed, 120 insertions(+), 101 deletions(-) diff --git a/deploy/build_chain.sh b/deploy/build_chain.sh index 6972f1c..069b485 100644 --- a/deploy/build_chain.sh +++ b/deploy/build_chain.sh @@ -907,7 +907,7 @@ download_bin() Download_Link="https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v${compatibility_version}/${package_name}" LOG_INFO "Downloading fisco-bcos binary from ${Download_Link} ..." if [ $(curl -IL -o /dev/null -s -w %{http_code} https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}) == 200 ];then - curl -LO ${Download_Link} --speed-time 30 --speed-limit 1024 -m ${download_timeout} || { + curl -LO ${Download_Link} --speed-time 3 --speed-limit 100 -m ${download_timeout} || { LOG_INFO "Download speed is too low, try https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}" curl -LO https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name} } diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 4cd8682..244b013 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -13,16 +13,17 @@ def do(): print "===================== deploy start... =====================" installNode() installWeb() - installMgr() + installManager() installFront() print "===================== deploy end... =====================" - print "===================== version V1.0.0 =====================" + print "===================== version V1.0.1 =====================" + print "================================================================" return def end(): stopNode() stopWeb() - stopMgr() + stopManager() stopFront() return @@ -35,7 +36,8 @@ def installNode(): node_counts = getCommProperties("node.counts") if if_exist_fisco == "no": - print "============== node install... ==============" + print "================================================================" + print "============== FISCO-BCOS install... ==============" # init configure file if not os.path.exists(currentDir + "/nodetemp"): doCmd('cp -f nodeconf nodetemp') @@ -55,13 +57,13 @@ def installNode(): # if result_build["status"] == 0: # if_build = 'completed' in result_build["output"] # if not if_build: - # print "======= node build fail! =======" + # print "======= FISCO-BCOS build fail! =======" # sys.exit(0) # else: - # print "======= node build fail! =======" + # print "======= FISCO-BCOS build fail! =======" # sys.exit(0) else: - info = raw_input("节点目录nodes已经存在。是否重新安装?[y/n]:") + info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") if info == "y" or info == "Y": doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") @@ -71,20 +73,20 @@ def installNode(): startNode() def startNode(): - print "============== node start... ==============" + print "============== FISCO-BCOS start... ==============" if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") if if_exist_fisco == "no": fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/start_all.sh"): - print "======= fisco dir:{} is not correct. please check! =======".format(fisco_dir) + print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash start_all.sh") - print "============== node end... ==============" + print "============== FISCO-BCOS end... ==============" return def stopNode(): @@ -94,7 +96,7 @@ def stopNode(): fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/stop_all.sh"): - print "======= fisco dir:{} is not correct. please check! =======".format(fisco_dir) + print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -128,16 +130,17 @@ def changeWebConfig(): return def installWeb(): - print "============== web install... ==============" + print "================================================================" + print "============== WeBASE-Web install... ==============" os.chdir(currentDir) pullSourceExtract("web.package.url","webase-web") changeWebConfig() startWeb() def startWeb(): - print "============== web start... ==============" + print "============== WeBASE-Web start... ==============" if os.path.exists("/run/nginx-webase-web.pid"): - info = raw_input("web进程已经存在,是否kill进程强制重启?[y/n]:") + info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": fin = open('/run/nginx-webase-web.pid', 'r') pid = fin.read() @@ -151,14 +154,14 @@ def startWeb(): if res["status"] == 0: res2 = doCmd("sudo " + res["output"] + " -c " + nginx_config_dir) if res2["status"] == 0: - print "======= web start success! =======" + print "======= WeBASE-Web start success! =======" else: - print "======= web start fail! =======" + print "======= WeBASE-Web start fail! =======" sys.exit(0) else: - print "======= web start fail! =======" + print "======= WeBASE-Web start fail! =======" sys.exit(0) - print "============== web end... ==============" + print "============== WeBASE-Web end... ==============" return def stopWeb(): @@ -168,12 +171,12 @@ def stopWeb(): cmd = "sudo kill -QUIT {}".format(pid) os.system(cmd) doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") - print "======= web stop success! =======" + print "======= WeBASE-Web stop success! =======" else: - print "======= web is not running! =======" + print "======= WeBASE-Web is not running! =======" return -def changeMgrConfig(): +def changeManagerConfig(): # get properties mgr_port = getCommProperties("mgr.port") mysql_ip = getCommProperties("mysql.ip") @@ -210,11 +213,12 @@ def changeMgrConfig(): return -def installMgr(): - print "============== mgr install... ==============" +def installManager(): + print "================================================================" + print "============== WeBASE-Node-Manager install... ==============" os.chdir(currentDir) pullSourceExtract("mgr.package.url","webase-node-mgr") - changeMgrConfig() + changeManagerConfig() dbConnect() mysql_ip = getCommProperties("mysql.ip") @@ -233,17 +237,17 @@ def installMgr(): if if_success: print "======= script init success! =======" else: - print "======= script init fail! =======" + print "======= script init fail! =======" print dbResult["output"] sys.exit(0) else: - print "======= script init fail! =======" + print "======= script init fail! =======" sys.exit(0) - startMgr() + startManager() return -def startMgr(): - print "============== mgr start... ==============" +def startManager(): + print "============== WeBASE-Node-Manager start... ==============" server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -253,37 +257,37 @@ def startMgr(): if result["status"] == 0: if_started = 'started' in result["output"] if if_started: - info = raw_input("mgr进程已经存在,是否kill进程强制重启?[y/n]:") + info = raw_input("WeBASE-Node-Manager进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": doCmd("bash stop.sh") result_start = doCmd("bash start.sh") if result_start["status"] == 0: if_success = 'Success' in result_start["output"] if if_success: - print "======= mgr start success! =======" - print "============== mgr end... ==============" + print "======= WeBASE-Node-Manager start success! =======" + print "============== WeBASE-Node-Manager end... ==============" return else: - print "======= mgr start fail! =======" + print "======= WeBASE-Node-Manager start fail! =======" sys.exit(0) else: - print "======= mgr start fail! =======" + print "======= WeBASE-Node-Manager start fail! =======" sys.exit(0) else: sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print "======= mgr start success! =======" + print "======= WeBASE-Node-Manager start success! =======" else: - print "======= mgr start fail! =======" + print "======= WeBASE-Node-Manager start fail! =======" sys.exit(0) else: - print "======= mgr start fail! =======" + print "======= WeBASE-Node-Manager start fail! =======" sys.exit(0) - print "============== mgr end... ==============" + print "============== WeBASE-Node-Manager end... ==============" return -def stopMgr(): +def stopManager(): server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -293,11 +297,11 @@ def stopMgr(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print "======= mgr stop success! =======" + print "======= WeBASE-Node-Manager stop success! =======" else: - print "======= mgr is not running! =======" + print "======= WeBASE-Node-Manager is not running! =======" else: - print "======= mgr stop fail! =======" + print "======= WeBASE-Node-Manager stop fail! =======" return def changeFrontConfig(): @@ -306,7 +310,6 @@ def changeFrontConfig(): mgr_port = getCommProperties("mgr.port") frontPort = getCommProperties("front.port") nodeChannelPort = getCommProperties("node.channelPort") - frontDb = getCommProperties("front.h2.db") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") @@ -327,13 +330,13 @@ def changeFrontConfig(): doCmd('sed -i "s/8081/{}/g" {}/application.yml'.format(frontPort, server_dir)) doCmd('sed -i "s/127.0.0.1:8080/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) - doCmd('sed -i "s%front_db%{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(fisco_dir, server_dir)) return def installFront(): - print "============== front install... ==============" + print "================================================================" + print "============== WeBASE-Front install... ==============" os.chdir(currentDir) pullSourceExtract("front.package.url","webase-front") changeFrontConfig() @@ -352,8 +355,9 @@ def installFront(): fisco_dir = getCommProperties("fisco.dir") if if_exist_fisco == "no": fisco_dir = currentDir + "/nodes/127.0.0.1" - if not os.path.exists(fisco_dir + "/sdk"): - print "======= fisco dir:{} is not correct. please check! =======".format(fisco_dir) + sdk_dir = fisco_dir + "/sdk" + if not os.path.exists(sdk_dir): + print "======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir) sys.exit(0) server_dir = currentDir + "/webase-front" os.chdir(server_dir) @@ -363,7 +367,7 @@ def installFront(): return def startFront(): - print "============== front start... ==============" + print "============== WeBASE-Front start... ==============" server_dir = currentDir + "/webase-front" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -373,34 +377,35 @@ def startFront(): if result["status"] == 0: if_started = 'started' in result["output"] if if_started: - info = raw_input("front进程已经存在,是否kill进程强制重启?[y/n]:") + info = raw_input("WeBASE-Front进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": doCmd("bash stop.sh") result_start = doCmd("bash start.sh") if result_start["status"] == 0: if_success = 'Success' in result_start["output"] if if_success: - print "======= front start success! =======" - print "============== front end... ==============" + print "======= WeBASE-Front start success! =======" + print "============== WeBASE-Front end... ==============" return else: - print "======= front start fail! =======" + print "======= WeBASE-Front start fail! =======" sys.exit(0) else: - print "======= front start fail! =======" + print "======= WeBASE-Front start fail! =======" sys.exit(0) else: sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print "======= front start success! =======" + print "======= WeBASE-Front start success! =======" else: - print "======= front start fail! =======" + print "======= WeBASE-Front start fail! =======" sys.exit(0) else: - print "======= front start fail! =======" + print "======= WeBASE-Front start fail! =======" sys.exit(0) - print "============== front end... ==============" + print "============== WeBASE-Front end... ==============" + print "================================================================" return def stopFront(): @@ -413,9 +418,9 @@ def stopFront(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print "======= front stop success! =======" + print "======= WeBASE-Front stop success! =======" else: - print "======= front is not running! =======" + print "======= WeBASE-Front is not running! =======" else: - print "======= front stop fail! =======" + print "======= WeBASE-Front stop fail! =======" return diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 307cb7b..24bec2d 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -9,6 +9,17 @@ checkDependent = ["git","openssl","curl","nginx"] def do(): + print "================================================================", + webaseMsg = ''' + _ _ ______ ___ _____ _____ + | | | | | ___ \/ _ \/ ___| ___| + | | | | ___| |_/ / /_\ \ `--.| |__ + | |/\| |/ _ | ___ | _ |`--. | __| + \ /\ | __| |_/ | | | /\__/ | |___ + \/ \/ \___\____/\_| |_\____/\____/ + ''' + print webaseMsg + print "================================================================" print "===================== envrionment check... =====================" installRequirements() checkSoft() @@ -18,6 +29,7 @@ def do(): checkFrontPort() checkDbConnect() print "===================== envrionment ready... =====================" + print "================================================================" def installRequirements(): for require in checkDependent: @@ -30,15 +42,23 @@ def installRequirements(): def checkSoft(): print "check java..." - res1 = doCmdIgnoreException("java -version") - if res1["status"] != 0: + res_check = doCmdIgnoreException("java -version") + if res_check["status"] != 0: print " error! java is not install or configure!" sys.exit(0) + if_openJDK = 'OpenJDK' in res_check["output"] + if if_openJDK: + print " error! OpenJDK cann't be supported, please change!" + sys.exit(0) + if_version = '1.8.' in res_check["output"] + if not if_version: + print " error! java version must be 1.8, please check!" + sys.exit(0) print "check finished sucessfully." return def checkNodePort(): - print "check node port..." + print "check FISCO-BCOS node port..." if_exist_fisco = getCommProperties("if.exist.fisco") if if_exist_fisco == "yes": checkExistedNodePort() @@ -90,7 +110,7 @@ def checkNewNodePort(): return def checkWebPort(): - print "check web port..." + print "check WeBASE-Web port..." deploy_ip = "127.0.0.1" web_port = getCommProperties("web.port") res_web = net_if_used(deploy_ip,web_port) @@ -100,7 +120,7 @@ def checkWebPort(): return def checkMgrPort(): - print "check mgr port..." + print "check WeBASE-Node-Manager port..." deploy_ip = "127.0.0.1" mgr_port = getCommProperties("mgr.port") res_mgr = net_if_used(deploy_ip,mgr_port) @@ -110,7 +130,7 @@ def checkMgrPort(): return def checkFrontPort(): - print "check front port..." + print "check WeBASE-Front port..." deploy_ip = "127.0.0.1" front_port = getCommProperties("front.port") res_front = net_if_used(deploy_ip,front_port) @@ -120,7 +140,7 @@ def checkFrontPort(): return def checkDbConnect(): - print "check db connection..." + print "check database connection..." mysql_ip = getCommProperties("mysql.ip") mysql_port = getCommProperties("mysql.port") ifLink = do_telnet(mysql_ip,mysql_port) @@ -129,13 +149,6 @@ def checkDbConnect(): sys.exit(0) print "check finished sucessfully." return - -def checkSdkDir(): - print "checking sdk dir" - nodeDir = getCommProperties("node.sdkDir") - if not os.path.exists(nodeDir): - print "{} is not exists".format(nodeDir) - return def hasInstallServer(server): result = doCmdIgnoreException("which {}".format(server)) diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index f018070..8b5f4ce 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -27,7 +27,7 @@ def dbConnect(): drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) if result == 1: - info = raw_input("mgr数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = raw_input("数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) if info == "y" or info == "Y": log.info(drop_db) cursor.execute(drop_db) diff --git a/deploy/common.properties b/deploy/common.properties index 0014082..f5d24cd 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,10 +1,11 @@ -[common] -# 编译包url,不用修改 -web.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-web.zip -mgr.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-node-mgr.zip -front.package.url=https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/WeBASEV1.0.0/webase-front.zip +[common] -# 数据库信息 +# 下载链接,默认不修改 +web.package.url=https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/WeBASE/V1.0.1/webase-web.zip +mgr.package.url=https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/WeBASE/V1.0.1/webase-node-mgr.zip +front.package.url=https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/WeBASE/V1.0.1/webase-front.zip + +# 数据库配置 mysql.ip=127.0.0.1 mysql.port=3306 mysql.user=dbUsername @@ -16,17 +17,17 @@ web.port=8080 mgr.port=8081 front.port=8082 -# 节点端口信息 +# 节点端口配置 node.p2pPort=30300 node.channelPort=20200 node.rpcPort=8545 -# 是否使用已有的链(yes/no) +# 是否使用已有的链 if.exist.fisco=yes # 使用已有链时需配置(已有链的路径,start_all.sh脚本所在路径) fisco.dir=fiscoDir # 搭建新链时需配置 -fisco.version=2.0.0-rc3 +fisco.version=2.0.0 node.counts=nodeCounts \ No newline at end of file diff --git a/deploy/deploy.py b/deploy/deploy.py index 3787cbc..028a613 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -21,10 +21,10 @@ def do(): commBuild.startWeb() elif "stopWeb" == param: commBuild.stopWeb() - elif "startMgr" == param: - commBuild.startMgr() - elif "stopMgr" == param: - commBuild.stopMgr() + elif "startManager" == param: + commBuild.startManager() + elif "stopManager" == param: + commBuild.stopManager() elif "startFront" == param: commBuild.startFront() elif "stopFront" == param: @@ -42,17 +42,17 @@ def help(): Usage: python deploy [Parameter] Parameter: - check : check the environment - installAll : check the environment, deploy all server - stopAll : stop all server - startNode : start nodes - stopNode : stop nodes - startWeb : start web server - stopWeb : stop web server - startMgr : start mgr server - stopMgr : stop mgr server - startFront : start front server - stopFront : stop front server + check: check the environment + installAll: check the environment, deploy all server + stopAll: stop all server + startNode: start FISCO-BCOS nodes + stopNode: stop FISCO-BCOS nodes + startWeb: start WeBASE-Web server + stopWeb: stop WeBASE-Web server + startManager: start WeBASE-Node-Manager server + stopManager: stop WeBASE-Node-Manager server + startFront: start WeBASE-Front server + stopFront: stop WeBASE-Front server Attention: 1. Need to install python2.7, jdk1.8, mysql 5.6, MySQL-python first @@ -63,8 +63,8 @@ def help(): return def paramError(): - print "Param error! Please check." print "" + print "Param error! Please check." help() return From ea08e4658a2ef4f1b209ad30a7edcbe08e11e2b4 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Thu, 11 Jul 2019 19:26:02 +0800 Subject: [PATCH 051/119] modify url --- deploy/common.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index f5d24cd..6658be2 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,9 +1,9 @@ [common] # 下载链接,默认不修改 -web.package.url=https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/WeBASE/V1.0.1/webase-web.zip -mgr.package.url=https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/WeBASE/V1.0.1/webase-node-mgr.zip -front.package.url=https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/WeBASE/V1.0.1/webase-front.zip +web.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-web.zip +mgr.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-node-mgr.zip +front.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-front.zip # 数据库配置 mysql.ip=127.0.0.1 From ecc6d54c7f1907df57f3ca63015a5736a6cd096a Mon Sep 17 00:00:00 2001 From: linbin524 <244875489@qq.com> Date: Tue, 16 Jul 2019 11:05:26 +0800 Subject: [PATCH 052/119] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8c768d1..a6f6272 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,4 @@ WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需 ## 社区 联系我们:webase@webank.com + From ce85896896c0c8673d612ac012c149c1f80e06b8 Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Thu, 18 Jul 2019 18:48:51 +0800 Subject: [PATCH 053/119] Create .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2ae2251 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh linguist-language=JAVA From 2b57e8b7fb09164d7f583741d2c524a52efaa02f Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 23 Jul 2019 17:47:44 +0800 Subject: [PATCH 054/119] fix bugs:add node.listenIp;modify server port;check front's db. --- deploy/build_chain.sh | 2 +- deploy/comm/build.py | 42 +++++++++++++++++++++------------------- deploy/comm/check.py | 24 ++++++++--------------- deploy/comm/mysql.py | 2 +- deploy/comm/nginx.conf | 4 ++-- deploy/common.properties | 25 ++++++++++++++++-------- 6 files changed, 51 insertions(+), 48 deletions(-) diff --git a/deploy/build_chain.sh b/deploy/build_chain.sh index 069b485..cfac629 100644 --- a/deploy/build_chain.sh +++ b/deploy/build_chain.sh @@ -907,7 +907,7 @@ download_bin() Download_Link="https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v${compatibility_version}/${package_name}" LOG_INFO "Downloading fisco-bcos binary from ${Download_Link} ..." if [ $(curl -IL -o /dev/null -s -w %{http_code} https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}) == 200 ];then - curl -LO ${Download_Link} --speed-time 3 --speed-limit 100 -m ${download_timeout} || { + curl -LO ${Download_Link} --speed-time 3 --speed-limit 204800 -m ${download_timeout} || { LOG_INFO "Download speed is too low, try https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}" curl -LO https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name} } diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 244b013..e831db2 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -16,7 +16,7 @@ def do(): installManager() installFront() print "===================== deploy end... =====================" - print "===================== version V1.0.1 =====================" + print "===================== version V1.0.2 =====================" print "================================================================" return @@ -70,7 +70,7 @@ def installNode(): doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - startNode() + startNode() def startNode(): print "============== FISCO-BCOS start... ==============" @@ -122,8 +122,8 @@ def changeWebConfig(): web_log_dir = web_dir + "/log" doCmd('mkdir -p {}'.format(web_log_dir)) doCmd('sed -i "s/127.0.0.1/{}/g" {}/comm/nginx.conf'.format(deploy_ip, currentDir)) - doCmd('sed -i "s/3002/{}/g" {}/comm/nginx.conf'.format(web_port, currentDir)) - doCmd('sed -i "s/10.0.0.1:8083/{}:{}/g" {}/comm/nginx.conf'.format(deploy_ip, mgr_port, currentDir)) + doCmd('sed -i "s/5000/{}/g" {}/comm/nginx.conf'.format(web_port, currentDir)) + doCmd('sed -i "s/10.0.0.1:5001/{}:{}/g" {}/comm/nginx.conf'.format(deploy_ip, mgr_port, currentDir)) doCmd('sed -i "s:log_path:{}:g" {}/comm/nginx.conf'.format(web_log_dir, currentDir)) doCmd('sed -i "s:web_page_url:{}:g" {}/comm/nginx.conf'.format(web_dir, currentDir)) @@ -154,12 +154,12 @@ def startWeb(): if res["status"] == 0: res2 = doCmd("sudo " + res["output"] + " -c " + nginx_config_dir) if res2["status"] == 0: - print "======= WeBASE-Web start success! =======" + print "======= WeBASE-Web start success! =======" else: - print "======= WeBASE-Web start fail! =======" + print "======= WeBASE-Web start fail! =======" sys.exit(0) else: - print "======= WeBASE-Web start fail! =======" + print "======= WeBASE-Web start fail! =======" sys.exit(0) print "============== WeBASE-Web end... ==============" return @@ -171,7 +171,7 @@ def stopWeb(): cmd = "sudo kill -QUIT {}".format(pid) os.system(cmd) doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") - print "======= WeBASE-Web stop success! =======" + print "======= WeBASE-Web stop success! =======" else: print "======= WeBASE-Web is not running! =======" return @@ -204,12 +204,12 @@ def changeManagerConfig(): doCmd('sed -i "s/fisco-bcos-data/{}/g" {}/webase.sh'.format(mysql_database, script_dir)) # change server config - doCmd('sed -i "s/8080/{}/g" {}/application.yml'.format(mgr_port, conf_dir)) + doCmd('sed -i "s/5001/{}/g" {}/application.yml'.format(mgr_port, conf_dir)) doCmd('sed -i "s/127.0.0.1/{}/g" {}/application.yml'.format(mysql_ip, conf_dir)) doCmd('sed -i "s/3306/{}/g" {}/application.yml'.format(mysql_port, conf_dir)) doCmd('sed -i "s/defaultAccount/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) doCmd('sed -i "s/defaultPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) - doCmd('sed -i "s/fisco-bcos-data/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) + doCmd('sed -i "s/webasenodemanager/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) return @@ -309,6 +309,7 @@ def changeFrontConfig(): deploy_ip = "127.0.0.1" mgr_port = getCommProperties("mgr.port") frontPort = getCommProperties("front.port") + nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") if_exist_fisco = getCommProperties("if.exist.fisco") @@ -326,11 +327,12 @@ def changeFrontConfig(): doCmd('cp -f {}/temp.yml {}/application.yml'.format(server_dir, server_dir)) # change server config + doCmd('sed -i "s/5002/{}/g" {}/application.yml'.format(frontPort, server_dir)) + doCmd('sed -i "s/0.0.0.0/{}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) - doCmd('sed -i "s/8081/{}/g" {}/application.yml'.format(frontPort, server_dir)) - doCmd('sed -i "s/127.0.0.1:8080/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) - doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) + doCmd('sed -i "s/127.0.0.1:5001/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(fisco_dir, server_dir)) + doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) return @@ -342,13 +344,13 @@ def installFront(): changeFrontConfig() # check front db - # frontDb = getCommProperties("front.h2.db") - # db_dir = currentDir+"/h2" - # res_file = checkFileName(db_dir,frontDb) - # if res_file: - # info = raw_input("front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) - # if info == "y" or info == "Y": - # doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) + frontDb = "webasefront" + db_dir = currentDir+"/h2" + res_file = checkFileName(db_dir,frontDb) + if res_file: + info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + if info == "y" or info == "Y": + doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) # copy node crt if_exist_fisco = getCommProperties("if.exist.fisco") diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 24bec2d..ce2c0d1 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -46,14 +46,6 @@ def checkSoft(): if res_check["status"] != 0: print " error! java is not install or configure!" sys.exit(0) - if_openJDK = 'OpenJDK' in res_check["output"] - if if_openJDK: - print " error! OpenJDK cann't be supported, please change!" - sys.exit(0) - if_version = '1.8.' in res_check["output"] - if not if_version: - print " error! java version must be 1.8, please check!" - sys.exit(0) print "check finished sucessfully." return @@ -70,26 +62,26 @@ def checkNodePort(): print "check finished sucessfully." def checkExistedNodePort(): - deploy_ip = "127.0.0.1" + listen_ip = getCommProperties("node.listenIp") node_rpcPort = int(getCommProperties("node.rpcPort")) node_p2pPort = int(getCommProperties("node.p2pPort")) node_channelPort = int(getCommProperties("node.channelPort")) - res_rpcPort = net_if_used_no_msg("127.0.0.1",node_rpcPort) + res_rpcPort = net_if_used_no_msg(listen_ip,node_rpcPort) if not res_rpcPort: print " error! rpc port {} is not alive. please check.".format(node_rpcPort) sys.exit(0) - res_p2pPort = net_if_used_no_msg("127.0.0.1",node_p2pPort) + res_p2pPort = net_if_used_no_msg(listen_ip,node_p2pPort) if not res_p2pPort: print " error! p2p port {} is not alive. please check.".format(node_p2pPort) sys.exit(0) - res_channelPort = net_if_used_no_msg("127.0.0.1",node_channelPort) + res_channelPort = net_if_used_no_msg(listen_ip,node_channelPort) if not res_channelPort: print " error! channel port {} is not alive. please check.".format(node_channelPort) sys.exit(0) return def checkNewNodePort(): - deploy_ip = "127.0.0.1" + listen_ip = getCommProperties("node.listenIp") nodes = getCommProperties("node.counts") node_counts = 2 if nodes != "nodeCounts": @@ -98,13 +90,13 @@ def checkNewNodePort(): node_p2pPort = int(getCommProperties("node.p2pPort")) node_channelPort = int(getCommProperties("node.channelPort")) for i in range(node_counts): - res_rpcPort = net_if_used("127.0.0.1",node_rpcPort+i) + res_rpcPort = net_if_used(listen_ip,node_rpcPort+i) if res_rpcPort: sys.exit(0) - res_p2pPort = net_if_used("127.0.0.1",node_p2pPort+i) + res_p2pPort = net_if_used(listen_ip,node_p2pPort+i) if res_p2pPort: sys.exit(0) - res_channelPort = net_if_used("127.0.0.1",node_channelPort+i) + res_channelPort = net_if_used(listen_ip,node_channelPort+i) if res_channelPort: sys.exit(0) return diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index 8b5f4ce..7e83bbf 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -27,7 +27,7 @@ def dbConnect(): drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) if result == 1: - info = raw_input("数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) if info == "y" or info == "Y": log.info(drop_db) cursor.execute(drop_db) diff --git a/deploy/comm/nginx.conf b/deploy/comm/nginx.conf index 09fcac2..f011003 100644 --- a/deploy/comm/nginx.conf +++ b/deploy/comm/nginx.conf @@ -28,10 +28,10 @@ http { #gzip on; add_header X-Frame-Options SAMEORIGIN; upstream node_mgr_server{ - server 10.0.0.1:8083; + server 10.0.0.1:5001; } server { - listen 3002 default_server; + listen 5000 default_server; server_name 127.0.0.1; location / { root web_page_url; diff --git a/deploy/common.properties b/deploy/common.properties index 6658be2..c3a0a78 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -6,28 +6,37 @@ mgr.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/weba front.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-front.zip # 数据库配置 -mysql.ip=127.0.0.1 +mysql.ip=localhost mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword -mysql.database=db_mgr +mysql.database=webasenodemanager -# 各个服务的端口 -web.port=8080 -mgr.port=8081 -front.port=8082 +# WeBASE管理平台服务端口 +web.port=5000 +# 节点管理子系统服务端口 +mgr.port=5001 +# 节点前置子系统端口 +front.port=5002 -# 节点端口配置 +# 节点监听Ip +node.listenIp=127.0.0.1 +# 节点p2p端口 node.p2pPort=30300 +# 节点链上链下端口 node.channelPort=20200 +# 节点rpc端口 node.rpcPort=8545 # 是否使用已有的链 if.exist.fisco=yes -# 使用已有链时需配置(已有链的路径,start_all.sh脚本所在路径) +# 使用已有链时需配置 +# 已有链的路径,start_all.sh脚本所在路径 fisco.dir=fiscoDir # 搭建新链时需配置 +# FISCO-BCOS版本 fisco.version=2.0.0 +# 搭建节点个数(默认两个) node.counts=nodeCounts \ No newline at end of file From bc9c8d273192cee76b6c2f444a688cff9b6a8dec Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 26 Jul 2019 15:19:46 +0800 Subject: [PATCH 055/119] modify db name --- deploy/comm/build.py | 2 +- deploy/comm/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index e831db2..04e77af 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -201,7 +201,7 @@ def changeManagerConfig(): # change script config doCmd('sed -i "s/defaultAccount/{}/g" {}/webase.sh'.format(mysql_user, script_dir)) doCmd('sed -i "s/defaultPassword/{}/g" {}/webase.sh'.format(mysql_password, script_dir)) - doCmd('sed -i "s/fisco-bcos-data/{}/g" {}/webase.sh'.format(mysql_database, script_dir)) + doCmd('sed -i "s/webasenodemanager/{}/g" {}/webase.sh'.format(mysql_database, script_dir)) # change server config doCmd('sed -i "s/5001/{}/g" {}/application.yml'.format(mgr_port, conf_dir)) diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index 535af8a..9c61bb7 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -172,7 +172,7 @@ def pullSourceExtract(urlName,fileName): sys.exit(0) else: info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) - if info == "y" or info == "Y": + if info1 == "y" or info1 == "Y": doCmd("rm -rf {}".format(fileName)) doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): From b180b29e57a0cf017596bc238db3601b9a86eb4c Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 2 Aug 2019 10:33:32 +0800 Subject: [PATCH 056/119] modify url --- deploy/common.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index c3a0a78..958e3ba 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,9 +1,9 @@ [common] # 下载链接,默认不修改 -web.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-web.zip -mgr.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-node-mgr.zip -front.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/V1.0.1/webase-front.zip +web.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-web.zip +mgr.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-node-mgr.zip +front.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-front.zip # 数据库配置 mysql.ip=localhost From ea23c6a4e2677b89b872c9658ffb1f54b637cc98 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Thu, 8 Aug 2019 15:52:19 +0800 Subject: [PATCH 057/119] Update quick-start.md --- quick-start.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quick-start.md b/quick-start.md index d667a73..1992a14 100644 --- a/quick-start.md +++ b/quick-start.md @@ -1,7 +1,7 @@ # 使用WeBASE开发区块链应用 ## 1 部署WeBASE -搭建WeBASE, 请参考[快速部署](https://) +搭建WeBASE, 请参考[快速部署](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) ## 2 登录WeBASE管理平台进行配置 安装WeBASE完成后,需要将节点信息添加到WeBASE平台中,这样WeBASE才可和节点进行通信。需要添加的信息包含节点信息,生成用户的私钥等。如下图所示: @@ -42,7 +42,7 @@ contract HelloWorld{ ## 4 应用层开发 ### 4.1 根据所写合约和交易api的格式,发送交易。 -请参考[交易接口](https://) +请参考[交易接口](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE-Front/interface.html#id222) 从IDE中的输出信息,拷贝合约地址,合约名,方法名等信息,同时获取用户的公钥信息,调用交易接口。 具体代码请参考 [HelloWorld范例](https://github.com/WeBankFinTech/WeBASE/tree/master/quick-start) From 95be1114bc425274d5266db1e983648de3f5db69 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Thu, 8 Aug 2019 18:41:06 +0800 Subject: [PATCH 058/119] modify config --- deploy/comm/build.py | 14 ++++++-------- deploy/comm/check.py | 17 +++++++++++++---- deploy/comm/nginx.conf | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 04e77af..69ce098 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -123,7 +123,7 @@ def changeWebConfig(): doCmd('mkdir -p {}'.format(web_log_dir)) doCmd('sed -i "s/127.0.0.1/{}/g" {}/comm/nginx.conf'.format(deploy_ip, currentDir)) doCmd('sed -i "s/5000/{}/g" {}/comm/nginx.conf'.format(web_port, currentDir)) - doCmd('sed -i "s/10.0.0.1:5001/{}:{}/g" {}/comm/nginx.conf'.format(deploy_ip, mgr_port, currentDir)) + doCmd('sed -i "s/server 127.0.0.1:5001/server {}:{}/g" {}/comm/nginx.conf'.format(deploy_ip, mgr_port, currentDir)) doCmd('sed -i "s:log_path:{}:g" {}/comm/nginx.conf'.format(web_log_dir, currentDir)) doCmd('sed -i "s:web_page_url:{}:g" {}/comm/nginx.conf'.format(web_dir, currentDir)) @@ -319,8 +319,6 @@ def changeFrontConfig(): # init file server_dir = currentDir + "/webase-front/conf" - db_dir = currentDir + "/h2" - doCmdIgnoreException("mkdir -p {}".format(db_dir)) if not os.path.exists(server_dir + "/temp.yml"): doCmd('cp -f {}/application.yml {}/temp.yml'.format(server_dir, server_dir)) else: @@ -328,11 +326,10 @@ def changeFrontConfig(): # change server config doCmd('sed -i "s/5002/{}/g" {}/application.yml'.format(frontPort, server_dir)) - doCmd('sed -i "s/0.0.0.0/{}/g" {}/application.yml'.format(nodeListenIp, server_dir)) + doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) - doCmd('sed -i "s/127.0.0.1:5001/{}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) - doCmd('sed -i "s%/data%{}%g" {}/application.yml'.format(fisco_dir, server_dir)) - doCmd('sed -i "s%h2Path%{}%g" {}/application.yml'.format(db_dir, server_dir)) + doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) + doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) return @@ -345,7 +342,8 @@ def installFront(): # check front db frontDb = "webasefront" - db_dir = currentDir+"/h2" + db_dir = currentDir+"/webase-front/h2" + doCmdIgnoreException("mkdir -p {}".format(db_dir)) res_file = checkFileName(db_dir,frontDb) if res_file: info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) diff --git a/deploy/comm/check.py b/deploy/comm/check.py index ce2c0d1..533c2e4 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -6,7 +6,7 @@ from utils import * log = deployLog.getLogger() -checkDependent = ["git","openssl","curl","nginx"] +checkDependent = ["git","openssl","curl"] def do(): print "================================================================", @@ -22,7 +22,8 @@ def do(): print "================================================================" print "===================== envrionment check... =====================" installRequirements() - checkSoft() + checkNginx() + checkJava() checkNodePort() checkWebPort() checkMgrPort() @@ -30,7 +31,7 @@ def do(): checkDbConnect() print "===================== envrionment ready... =====================" print "================================================================" - + def installRequirements(): for require in checkDependent: print "check {}...".format(require) @@ -39,8 +40,16 @@ def installRequirements(): installByYum(require) print "check finished sucessfully." return + +def checkNginx(): + print "check nginx..." + require = "nginx" + hasInstall = hasInstallServer(require) + if not hasInstall: + installByYum(require) + print "check finished sucessfully." -def checkSoft(): +def checkJava(): print "check java..." res_check = doCmdIgnoreException("java -version") if res_check["status"] != 0: diff --git a/deploy/comm/nginx.conf b/deploy/comm/nginx.conf index f011003..41a25e5 100644 --- a/deploy/comm/nginx.conf +++ b/deploy/comm/nginx.conf @@ -28,7 +28,7 @@ http { #gzip on; add_header X-Frame-Options SAMEORIGIN; upstream node_mgr_server{ - server 10.0.0.1:5001; + server 127.0.0.1:5001; } server { listen 5000 default_server; From 0229249341e2e5bd171b27aa30be6b4292a215d3 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Mon, 12 Aug 2019 10:33:27 +0800 Subject: [PATCH 059/119] modify url --- deploy/comm/check.py | 6 +++++- deploy/common.properties | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 533c2e4..5ecdc02 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -53,7 +53,11 @@ def checkJava(): print "check java..." res_check = doCmdIgnoreException("java -version") if res_check["status"] != 0: - print " error! java is not install or configure!" + print " error! java has not been installed or configured!" + sys.exit(0) + res_home = doCmd("echo $JAVA_HOME") + if res_home["output"].strip() == "": + print " error! JAVA_HOME has not been configured!" sys.exit(0) print "check finished sucessfully." return diff --git a/deploy/common.properties b/deploy/common.properties index 958e3ba..6270af6 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,9 +1,9 @@ [common] # 下载链接,默认不修改 -web.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-web.zip -mgr.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-node-mgr.zip -front.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-front.zip +web.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-web.zip +mgr.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-node-mgr.zip +front.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-front.zip # 数据库配置 mysql.ip=localhost From 4318c618425039060f0207f3e6e34c363486fa2f Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Mon, 12 Aug 2019 17:26:15 +0800 Subject: [PATCH 060/119] front db can be configed --- deploy/comm/build.py | 8 ++++++-- deploy/common.properties | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 69ce098..1a33a06 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -21,7 +21,9 @@ def do(): return def end(): - stopNode() + if_exist_fisco = getCommProperties("if.exist.fisco") + if if_exist_fisco == "no": + stopNode() stopWeb() stopManager() stopFront() @@ -311,6 +313,7 @@ def changeFrontConfig(): frontPort = getCommProperties("front.port") nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") + frontDb = getCommProperties("front.h2.name") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") @@ -329,6 +332,7 @@ def changeFrontConfig(): doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) + doCmd('sed -i "s%webasefront%{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) return @@ -341,7 +345,7 @@ def installFront(): changeFrontConfig() # check front db - frontDb = "webasefront" + frontDb = getCommProperties("front.h2.name") db_dir = currentDir+"/webase-front/h2" doCmdIgnoreException("mkdir -p {}".format(db_dir)) res_file = checkFileName(db_dir,frontDb) diff --git a/deploy/common.properties b/deploy/common.properties index 6270af6..e336c56 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -5,13 +5,16 @@ web.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/web mgr.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-node-mgr.zip front.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-front.zip -# 数据库配置 +# 节点管理子系统mysql数据库配置 mysql.ip=localhost mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword mysql.database=webasenodemanager +# 节点前置子系统h2数据库名 +front.h2.name=webasefront + # WeBASE管理平台服务端口 web.port=5000 # 节点管理子系统服务端口 From 6fa4a0e9730ad5ca1ac4728c6fe976b1ca05f937 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Wed, 14 Aug 2019 15:18:36 +0800 Subject: [PATCH 061/119] modify url --- quick-start/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quick-start/src/main/resources/application.yml b/quick-start/src/main/resources/application.yml index dfa9c85..10f80c1 100644 --- a/quick-start/src/main/resources/application.yml +++ b/quick-start/src/main/resources/application.yml @@ -1,4 +1,4 @@ -transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle +transactionUrl: http://127.0.0.1:5002/WeBASE-Front/trans/handle groupId: 1 userAddress: "0x4f08eac5af5e77b7006d11bee94adba2f721def8" useAes: true From 4c3b8b82d87936b7d367d12a9ed83b4335d829de Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Mon, 19 Aug 2019 10:50:17 +0800 Subject: [PATCH 062/119] support python3 --- deploy/comm/__init__.py | 6 ++ deploy/comm/build.py | 211 +++++++++++++++++++-------------------- deploy/comm/check.py | 69 ++++++------- deploy/comm/log.py | 8 +- deploy/comm/mysql.py | 14 ++- deploy/comm/utils.py | 69 +++++++++---- deploy/common.properties | 14 ++- deploy/deploy.py | 11 +- 8 files changed, 218 insertions(+), 184 deletions(-) diff --git a/deploy/comm/__init__.py b/deploy/comm/__init__.py index e69de29..fc51ec7 100644 --- a/deploy/comm/__init__.py +++ b/deploy/comm/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +if sys.version_info.major == 3: + import pymysql + pymysql.install_as_MySQLdb() \ No newline at end of file diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 1a33a06..2c4e998 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -1,29 +1,29 @@ -#!/usr/bin/python +#!/usr/bin/python3 # encoding: utf-8 import sys import os -from utils import * -from mysql import * +from .utils import * +from .mysql import dbConnect baseDir = getBaseDir() currentDir = getCurrentBaseDir() def do(): - print "===================== deploy start... =====================" + print ("===================== deploy start... =====================") installNode() installWeb() installManager() installFront() - print "===================== deploy end... =====================" - print "===================== version V1.0.2 =====================" - print "================================================================" + print ("===================== deploy end... =====================") + os.chdir(currentDir) + version = getCommProperties("webase.version") + print ("===================== version {} =====================".format(version)) + print ("================================================================") return def end(): - if_exist_fisco = getCommProperties("if.exist.fisco") - if if_exist_fisco == "no": - stopNode() + stopNode() stopWeb() stopManager() stopFront() @@ -38,8 +38,8 @@ def installNode(): node_counts = getCommProperties("node.counts") if if_exist_fisco == "no": - print "================================================================" - print "============== FISCO-BCOS install... ==============" + print ("================================================================") + print ("============== FISCO-BCOS install... ==============") # init configure file if not os.path.exists(currentDir + "/nodetemp"): doCmd('cp -f nodeconf nodetemp') @@ -55,40 +55,35 @@ def installNode(): doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - # result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - # if result_build["status"] == 0: - # if_build = 'completed' in result_build["output"] - # if not if_build: - # print "======= FISCO-BCOS build fail! =======" - # sys.exit(0) - # else: - # print "======= FISCO-BCOS build fail! =======" - # sys.exit(0) else: - info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + info = "n" + if sys.version_info.major == 2: + info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + else: + info = input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") if info == "y" or info == "Y": doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - startNode() + startNode() def startNode(): - print "============== FISCO-BCOS start... ==============" + print ("============== FISCO-BCOS start... ==============") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") if if_exist_fisco == "no": fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/start_all.sh"): - print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) + print ("======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir)) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash start_all.sh") - print "============== FISCO-BCOS end... ==============" + print ("============== FISCO-BCOS end... ==============") return def stopNode(): @@ -98,7 +93,7 @@ def stopNode(): fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/stop_all.sh"): - print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) + print ("======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir)) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -132,17 +127,23 @@ def changeWebConfig(): return def installWeb(): - print "================================================================" - print "============== WeBASE-Web install... ==============" + print ("================================================================") + print ("============== WeBASE-Web install... ==============") os.chdir(currentDir) - pullSourceExtract("web.package.url","webase-web") + version = getCommProperties("webase.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(version) + pullSourceExtract(gitComm,"webase-web") changeWebConfig() startWeb() def startWeb(): - print "============== WeBASE-Web start... ==============" + print ("============== WeBASE-Web start... ==============") if os.path.exists("/run/nginx-webase-web.pid"): - info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + else: + info = input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": fin = open('/run/nginx-webase-web.pid', 'r') pid = fin.read() @@ -156,14 +157,14 @@ def startWeb(): if res["status"] == 0: res2 = doCmd("sudo " + res["output"] + " -c " + nginx_config_dir) if res2["status"] == 0: - print "======= WeBASE-Web start success! =======" + print ("======= WeBASE-Web start success! =======") else: - print "======= WeBASE-Web start fail! =======" + print ("======= WeBASE-Web start fail! =======") sys.exit(0) else: - print "======= WeBASE-Web start fail! =======" + print ("======= WeBASE-Web start fail! =======") sys.exit(0) - print "============== WeBASE-Web end... ==============" + print ("============== WeBASE-Web end... ==============") return def stopWeb(): @@ -173,9 +174,9 @@ def stopWeb(): cmd = "sudo kill -QUIT {}".format(pid) os.system(cmd) doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") - print "======= WeBASE-Web stop success! =======" + print ("======= WeBASE-Web stop success! =======") else: - print "======= WeBASE-Web is not running! =======" + print ("======= WeBASE-Web is not running! =======") return def changeManagerConfig(): @@ -216,10 +217,12 @@ def changeManagerConfig(): return def installManager(): - print "================================================================" - print "============== WeBASE-Node-Manager install... ==============" + print ("================================================================") + print ("============== WeBASE-Node-Manager install... ==============") os.chdir(currentDir) - pullSourceExtract("mgr.package.url","webase-node-mgr") + version = getCommProperties("webase.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) + pullSourceExtract(gitComm,"webase-node-mgr") changeManagerConfig() dbConnect() @@ -228,7 +231,11 @@ def installManager(): server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" - info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + info = "n" + if sys.version_info.major == 2: + info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + else: + info = input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") if info == "y" or info == "Y": os.chdir(script_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -237,19 +244,21 @@ def installManager(): if dbResult["status"] == 0: if_success = 'success' in dbResult["output"] if if_success: - print "======= script init success! =======" + print ("======= script init success! =======") else: - print "======= script init fail! =======" - print dbResult["output"] + print ("======= script init fail! =======") + print (dbResult["output"]) sys.exit(0) else: - print "======= script init fail! =======" + print ("======= script init fail! =======") sys.exit(0) startManager() return def startManager(): - print "============== WeBASE-Node-Manager start... ==============" + print ("============== WeBASE-Node-Manager start... ==============") + os.chdir(currentDir) + managerPort = getCommProperties("front.port") server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -257,36 +266,26 @@ def startManager(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_started = 'started' in result["output"] + if_occupied = 'been occupied' in result["output"] + if if_occupied: + pid = get_str_btw(result["output"], "(", ")") + print ("Port {} has been occupied by other server PID({})".format(managerPort,pid)) + sys.exit(0) + if_started = 'is running' in result["output"] if if_started: - info = raw_input("WeBASE-Node-Manager进程已经存在,是否kill进程强制重启?[y/n]:") - if info == "y" or info == "Y": - doCmd("bash stop.sh") - result_start = doCmd("bash start.sh") - if result_start["status"] == 0: - if_success = 'Success' in result_start["output"] - if if_success: - print "======= WeBASE-Node-Manager start success! =======" - print "============== WeBASE-Node-Manager end... ==============" - return - else: - print "======= WeBASE-Node-Manager start fail! =======" - sys.exit(0) - else: - print "======= WeBASE-Node-Manager start fail! =======" - sys.exit(0) - else: - sys.exit(0) + pid = get_str_btw(result["output"], "(", ")") + print ("WeBASE-Node-Manager Port {} is running PID({})".format(managerPort,pid)) + sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Node-Manager start success! =======" + print ("======= WeBASE-Node-Manager start success! =======") else: - print "======= WeBASE-Node-Manager start fail! =======" + print ("======= WeBASE-Node-Manager start fail! =======") sys.exit(0) else: - print "======= WeBASE-Node-Manager start fail! =======" + print ("======= WeBASE-Node-Manager start fail! =======") sys.exit(0) - print "============== WeBASE-Node-Manager end... ==============" + print ("============== WeBASE-Node-Manager end... ==============") return def stopManager(): @@ -299,11 +298,11 @@ def stopManager(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Node-Manager stop success! =======" + print ("======= WeBASE-Node-Manager stop success! =======") else: - print "======= WeBASE-Node-Manager is not running! =======" + print ("======= WeBASE-Node-Manager is not running! =======") else: - print "======= WeBASE-Node-Manager stop fail! =======" + print ("======= WeBASE-Node-Manager stop fail! =======") return def changeFrontConfig(): @@ -338,19 +337,25 @@ def changeFrontConfig(): return def installFront(): - print "================================================================" - print "============== WeBASE-Front install... ==============" + print ("================================================================") + print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) - pullSourceExtract("front.package.url","webase-front") + version = getCommProperties("webase.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) + pullSourceExtract(gitComm,"webase-front") changeFrontConfig() # check front db frontDb = getCommProperties("front.h2.name") - db_dir = currentDir+"/webase-front/h2" + db_dir = currentDir+"/h2" doCmdIgnoreException("mkdir -p {}".format(db_dir)) res_file = checkFileName(db_dir,frontDb) if res_file: - info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + else: + info = input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) if info == "y" or info == "Y": doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) @@ -361,7 +366,7 @@ def installFront(): fisco_dir = currentDir + "/nodes/127.0.0.1" sdk_dir = fisco_dir + "/sdk" if not os.path.exists(sdk_dir): - print "======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir) + print ("======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir)) sys.exit(0) server_dir = currentDir + "/webase-front" os.chdir(server_dir) @@ -371,7 +376,9 @@ def installFront(): return def startFront(): - print "============== WeBASE-Front start... ==============" + print ("============== WeBASE-Front start... ==============") + os.chdir(currentDir) + frontPort = getCommProperties("front.port") server_dir = currentDir + "/webase-front" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -379,37 +386,27 @@ def startFront(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_started = 'started' in result["output"] + if_occupied = 'been occupied' in result["output"] + if if_occupied: + pid = get_str_btw(result["output"], "(", ")") + print ("Port {} has been occupied by other server PID({})".format(frontPort,pid)) + sys.exit(0) + if_started = 'is running' in result["output"] if if_started: - info = raw_input("WeBASE-Front进程已经存在,是否kill进程强制重启?[y/n]:") - if info == "y" or info == "Y": - doCmd("bash stop.sh") - result_start = doCmd("bash start.sh") - if result_start["status"] == 0: - if_success = 'Success' in result_start["output"] - if if_success: - print "======= WeBASE-Front start success! =======" - print "============== WeBASE-Front end... ==============" - return - else: - print "======= WeBASE-Front start fail! =======" - sys.exit(0) - else: - print "======= WeBASE-Front start fail! =======" - sys.exit(0) - else: - sys.exit(0) + pid = get_str_btw(result["output"], "(", ")") + print ("WeBASE-Front Port {} is running PID({})".format(frontPort,pid)) + sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Front start success! =======" + print ("======= WeBASE-Front start success! =======") else: - print "======= WeBASE-Front start fail! =======" + print ("======= WeBASE-Front start fail! =======") sys.exit(0) else: - print "======= WeBASE-Front start fail! =======" + print ("======= WeBASE-Front start fail! =======") sys.exit(0) - print "============== WeBASE-Front end... ==============" - print "================================================================" + print ("============== WeBASE-Front end... ==============") + print ("================================================================") return def stopFront(): @@ -422,9 +419,9 @@ def stopFront(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Front stop success! =======" + print ("======= WeBASE-Front stop success! =======") else: - print "======= WeBASE-Front is not running! =======" + print ("======= WeBASE-Front is not running! =======") else: - print "======= WeBASE-Front stop fail! =======" + print ("======= WeBASE-Front stop fail! =======") return diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 5ecdc02..0065f7d 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -1,15 +1,15 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 -import log as deployLog +from . import log as deployLog import sys -from utils import * +from .utils import * -log = deployLog.getLogger() +log = deployLog.getLocalLogger() checkDependent = ["git","openssl","curl"] def do(): - print "================================================================", + print ("================================================================"), webaseMsg = ''' _ _ ______ ___ _____ _____ | | | | | ___ \/ _ \/ ___| ___| @@ -18,9 +18,9 @@ def do(): \ /\ | __| |_/ | | | /\__/ | |___ \/ \/ \___\____/\_| |_\____/\____/ ''' - print webaseMsg - print "================================================================" - print "===================== envrionment check... =====================" + print (webaseMsg) + print ("================================================================") + print ("===================== envrionment check... =====================") installRequirements() checkNginx() checkJava() @@ -29,50 +29,51 @@ def do(): checkMgrPort() checkFrontPort() checkDbConnect() - print "===================== envrionment ready... =====================" - print "================================================================" + print ("===================== envrionment ready... =====================") + print ("================================================================") def installRequirements(): for require in checkDependent: - print "check {}...".format(require) + print ("check {}...".format(require)) hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkNginx(): - print "check nginx..." + print ("check nginx...") require = "nginx" hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print "check finished sucessfully." + print ("check finished sucessfully.") def checkJava(): - print "check java..." + print ("check java...") res_check = doCmdIgnoreException("java -version") if res_check["status"] != 0: - print " error! java has not been installed or configured!" + print (" error! java has not been installed or configured!") sys.exit(0) res_home = doCmd("echo $JAVA_HOME") if res_home["output"].strip() == "": - print " error! JAVA_HOME has not been configured!" + print (" error! JAVA_HOME has not been configured!") sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkNodePort(): - print "check FISCO-BCOS node port..." if_exist_fisco = getCommProperties("if.exist.fisco") if if_exist_fisco == "yes": - checkExistedNodePort() + # checkExistedNodePort() + return elif if_exist_fisco == "no": + print ("check FISCO-BCOS node port...") checkNewNodePort() + print ("check finished sucessfully.") else: - print " error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco) + print (" error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco)) sys.exit(0) - print "check finished sucessfully." def checkExistedNodePort(): listen_ip = getCommProperties("node.listenIp") @@ -81,15 +82,15 @@ def checkExistedNodePort(): node_channelPort = int(getCommProperties("node.channelPort")) res_rpcPort = net_if_used_no_msg(listen_ip,node_rpcPort) if not res_rpcPort: - print " error! rpc port {} is not alive. please check.".format(node_rpcPort) + print (" error! rpc port {} is not alive. please check.".format(node_rpcPort)) sys.exit(0) res_p2pPort = net_if_used_no_msg(listen_ip,node_p2pPort) if not res_p2pPort: - print " error! p2p port {} is not alive. please check.".format(node_p2pPort) + print (" error! p2p port {} is not alive. please check.".format(node_p2pPort)) sys.exit(0) res_channelPort = net_if_used_no_msg(listen_ip,node_channelPort) if not res_channelPort: - print " error! channel port {} is not alive. please check.".format(node_channelPort) + print (" error! channel port {} is not alive. please check.".format(node_channelPort)) sys.exit(0) return @@ -115,44 +116,44 @@ def checkNewNodePort(): return def checkWebPort(): - print "check WeBASE-Web port..." + print ("check WeBASE-Web port...") deploy_ip = "127.0.0.1" web_port = getCommProperties("web.port") res_web = net_if_used(deploy_ip,web_port) if res_web: sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkMgrPort(): - print "check WeBASE-Node-Manager port..." + print ("check WeBASE-Node-Manager port...") deploy_ip = "127.0.0.1" mgr_port = getCommProperties("mgr.port") res_mgr = net_if_used(deploy_ip,mgr_port) if res_mgr: sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkFrontPort(): - print "check WeBASE-Front port..." + print ("check WeBASE-Front port...") deploy_ip = "127.0.0.1" front_port = getCommProperties("front.port") res_front = net_if_used(deploy_ip,front_port) if res_front: sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkDbConnect(): - print "check database connection..." + print ("check database connection...") mysql_ip = getCommProperties("mysql.ip") mysql_port = getCommProperties("mysql.port") ifLink = do_telnet(mysql_ip,mysql_port) if not ifLink: - print 'The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port) + print ('The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port)) sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def hasInstallServer(server): diff --git a/deploy/comm/log.py b/deploy/comm/log.py index 8ffda8e..6de1390 100644 --- a/deploy/comm/log.py +++ b/deploy/comm/log.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 import logging, os @@ -20,7 +20,7 @@ def info(self, message): self.logger.info(message) def infoPrint(self, mesage): - print mesage + print (mesage) self.logger.info(mesage) def war(self, message): @@ -34,7 +34,7 @@ def cri(self, message): loggermap = {} -def getLogger(): +def getLocalLogger(): logPath ="./log/" logName="info.log" isExists=os.path.exists(logPath) @@ -48,5 +48,5 @@ def getLogger(): return logger if __name__ == '__main__': - log = getLogger() + log = getLocalLogger() log.info("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv") \ No newline at end of file diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index 7e83bbf..7fde026 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -1,12 +1,12 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 -import log as deployLog +from . import log as deployLog import sys import MySQLdb as mdb -from utils import * +from .utils import * -log = deployLog.getLogger() +log = deployLog.getLocalLogger() def dbConnect(): # get properties @@ -27,7 +27,11 @@ def dbConnect(): drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) if result == 1: - info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + else: + info = input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) if info == "y" or info == "Y": log.info(drop_db) cursor.execute(drop_db) diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index 9c61bb7..d9209df 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -1,19 +1,29 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 -import ConfigParser -import commands -import log as deployLog +import os +import sys +try: + import ConfigParser +except: + try: + import configparser as ConfigParser + except: + from six.moves import configparser as ConfigParser +if sys.version_info.major == 2: + import commands +else: + import subprocess +from . import log as deployLog import socket import fcntl import struct import telnetlib -import os import platform import shutil from distutils.dir_util import copy_tree -log = deployLog.getLogger() +log = deployLog.getLocalLogger() platformStr = platform.platform() def getIpAddress(ifname): @@ -33,7 +43,7 @@ def net_if_used(ip,port): try: result=s.connect_ex((ip, int(port))) if result==0: - print " error! port {} has been used. please check.".format(port) + print (" error! port {} has been used. please check.".format(port)) return True else: return False @@ -80,7 +90,10 @@ def copytree(src, dst): def doCmd(cmd): log.info(" execute cmd start ,cmd : {}".format(cmd)) result = dict() - (status, output) = commands.getstatusoutput(cmd) + if sys.version_info.major == 2: + (status, output) = commands.getstatusoutput(cmd) + else: + (status, output) = subprocess.getstatusoutput(cmd) result["status"] = status result["output"] = output log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd,status,output)) @@ -91,7 +104,10 @@ def doCmd(cmd): def doCmdIgnoreException(cmd): log.info(" execute cmd start ,cmd : {}".format(cmd)) result = dict() - (status, output) = commands.getstatusoutput(cmd) + if sys.version_info.major == 2: + (status, output) = commands.getstatusoutput(cmd) + else: + (status, output) = subprocess.getstatusoutput(cmd) result["status"] = status result["output"] = output log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd, status, output)) @@ -109,7 +125,7 @@ def getCommProperties(paramsKey): def replaceConf(fileName,oldStr,newStr): if not os.path.isfile(fileName): - print "{} is not a file ".format(fileName) + print ("{} is not a file ".format(fileName)) return oldData ="" with open(fileName, "r") as f: @@ -123,7 +139,7 @@ def replaceConf(fileName,oldStr,newStr): def replaceConfDir(filePath,oldStr,newStr): if not os.path.isdir(filePath): - print "{} is not a dir ".format(filePath) + print ("{} is not a dir ".format(filePath)) return for root, dirs, files in os.walk(filePath): for file in files: @@ -153,30 +169,37 @@ def do_telnet(host,port): return False return True -def pullSourceExtract(urlName,fileName): - git_comm = "wget " + getCommProperties(urlName) +def pullSourceExtract(gitComm,fileName): if not os.path.exists("{}/{}.zip".format(getCurrentBaseDir(),fileName)): - print git_comm - os.system(git_comm) + print (gitComm) + os.system(gitComm) else: - info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + info = "n" + if sys.version_info.major == 2: + info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + else: + info = input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) if info == "y" or info == "Y": doCmd("rm -rf {}.zip".format(fileName)) doCmd("rm -rf {}".format(fileName)) - print git_comm - os.system(git_comm) + print (gitComm) + os.system(gitComm) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): - print "{}.zip extract failed!".format(fileName) + print ("{}.zip extract failed!".format(fileName)) sys.exit(0) else: - info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + info1 = "n" + if sys.version_info.major == 2: + info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + else: + info1 = input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) if info1 == "y" or info1 == "Y": doCmd("rm -rf {}".format(fileName)) doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): - print "{}.zip extract failed!".format(fileName) + print ("{}.zip extract failed!".format(fileName)) sys.exit(0) def checkFileName(dir,fileName): @@ -188,6 +211,10 @@ def checkFileName(dir,fileName): return True else: return False + +def get_str_btw(s, f, b): + par = s.partition(f) + return (par[2].partition(b))[0][:] if __name__ == '__main__': print(getIpAddress("eth0")) diff --git a/deploy/common.properties b/deploy/common.properties index e336c56..c696b48 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,9 +1,7 @@ [common] -# 下载链接,默认不修改 -web.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-web.zip -mgr.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-node-mgr.zip -front.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-front.zip +# WeBASE版本(v1.0.2或以上版本) +webase.version=v1.0.2 # 节点管理子系统mysql数据库配置 mysql.ip=localhost @@ -31,12 +29,12 @@ node.channelPort=20200 # 节点rpc端口 node.rpcPort=8545 -# 是否使用已有的链 -if.exist.fisco=yes +# 是否使用已有的链(yes/no) +if.exist.fisco=no # 使用已有链时需配置 -# 已有链的路径,start_all.sh脚本所在路径 -fisco.dir=fiscoDir +# 已有链的路径,start_all.sh脚本所在路径,路径下要存在sdk目录 +fisco.dir=/data/app/nodes/127.0.0.1 # 搭建新链时需配置 # FISCO-BCOS版本 diff --git a/deploy/deploy.py b/deploy/deploy.py index 028a613..218fe5f 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -1,5 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/python3 # encoding: utf-8 + import sys import comm.check as commCheck import comm.build as commBuild @@ -55,16 +56,16 @@ def help(): stopFront: stop WeBASE-Front server Attention: - 1. Need to install python2.7, jdk1.8, mysql 5.6, MySQL-python first + 1. Need to install python, jdk, mysql, MySQL-python or PyMySQL first 2. Need to ensure a smooth network 3. You need to install git, wget, nginx; if it is not installed, the installation script will automatically install these components, but this may fail. ''' - print helpMsg + print (helpMsg) return def paramError(): - print "" - print "Param error! Please check." + print ("") + print ("Param error! Please check.") help() return From ee466f5dd8ea309ce92eb99dcf15d023e20bdccf Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Fri, 23 Aug 2019 11:01:59 +0800 Subject: [PATCH 063/119] Revert "V1.1.0" --- deploy/comm/__init__.py | 6 - deploy/comm/build.py | 211 +++++++++--------- deploy/comm/check.py | 69 +++--- deploy/comm/log.py | 8 +- deploy/comm/mysql.py | 14 +- deploy/comm/utils.py | 69 ++---- deploy/common.properties | 19 +- deploy/deploy.py | 11 +- .../src/main/resources/application.yml | 2 +- 9 files changed, 184 insertions(+), 225 deletions(-) diff --git a/deploy/comm/__init__.py b/deploy/comm/__init__.py index fc51ec7..e69de29 100644 --- a/deploy/comm/__init__.py +++ b/deploy/comm/__init__.py @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import sys -if sys.version_info.major == 3: - import pymysql - pymysql.install_as_MySQLdb() \ No newline at end of file diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 2c4e998..69ce098 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -1,25 +1,23 @@ -#!/usr/bin/python3 +#!/usr/bin/python # encoding: utf-8 import sys import os -from .utils import * -from .mysql import dbConnect +from utils import * +from mysql import * baseDir = getBaseDir() currentDir = getCurrentBaseDir() def do(): - print ("===================== deploy start... =====================") + print "===================== deploy start... =====================" installNode() installWeb() installManager() installFront() - print ("===================== deploy end... =====================") - os.chdir(currentDir) - version = getCommProperties("webase.version") - print ("===================== version {} =====================".format(version)) - print ("================================================================") + print "===================== deploy end... =====================" + print "===================== version V1.0.2 =====================" + print "================================================================" return def end(): @@ -38,8 +36,8 @@ def installNode(): node_counts = getCommProperties("node.counts") if if_exist_fisco == "no": - print ("================================================================") - print ("============== FISCO-BCOS install... ==============") + print "================================================================" + print "============== FISCO-BCOS install... ==============" # init configure file if not os.path.exists(currentDir + "/nodetemp"): doCmd('cp -f nodeconf nodetemp') @@ -55,35 +53,40 @@ def installNode(): doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + # result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + # if result_build["status"] == 0: + # if_build = 'completed' in result_build["output"] + # if not if_build: + # print "======= FISCO-BCOS build fail! =======" + # sys.exit(0) + # else: + # print "======= FISCO-BCOS build fail! =======" + # sys.exit(0) else: - info = "n" - if sys.version_info.major == 2: - info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") - else: - info = input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") if info == "y" or info == "Y": doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - startNode() + startNode() def startNode(): - print ("============== FISCO-BCOS start... ==============") + print "============== FISCO-BCOS start... ==============" if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") if if_exist_fisco == "no": fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/start_all.sh"): - print ("======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir)) + print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash start_all.sh") - print ("============== FISCO-BCOS end... ==============") + print "============== FISCO-BCOS end... ==============" return def stopNode(): @@ -93,7 +96,7 @@ def stopNode(): fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/stop_all.sh"): - print ("======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir)) + print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -127,23 +130,17 @@ def changeWebConfig(): return def installWeb(): - print ("================================================================") - print ("============== WeBASE-Web install... ==============") + print "================================================================" + print "============== WeBASE-Web install... ==============" os.chdir(currentDir) - version = getCommProperties("webase.version") - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(version) - pullSourceExtract(gitComm,"webase-web") + pullSourceExtract("web.package.url","webase-web") changeWebConfig() startWeb() def startWeb(): - print ("============== WeBASE-Web start... ==============") + print "============== WeBASE-Web start... ==============" if os.path.exists("/run/nginx-webase-web.pid"): - info = "n" - if sys.version_info.major == 2: - info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") - else: - info = input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": fin = open('/run/nginx-webase-web.pid', 'r') pid = fin.read() @@ -157,14 +154,14 @@ def startWeb(): if res["status"] == 0: res2 = doCmd("sudo " + res["output"] + " -c " + nginx_config_dir) if res2["status"] == 0: - print ("======= WeBASE-Web start success! =======") + print "======= WeBASE-Web start success! =======" else: - print ("======= WeBASE-Web start fail! =======") + print "======= WeBASE-Web start fail! =======" sys.exit(0) else: - print ("======= WeBASE-Web start fail! =======") + print "======= WeBASE-Web start fail! =======" sys.exit(0) - print ("============== WeBASE-Web end... ==============") + print "============== WeBASE-Web end... ==============" return def stopWeb(): @@ -174,9 +171,9 @@ def stopWeb(): cmd = "sudo kill -QUIT {}".format(pid) os.system(cmd) doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") - print ("======= WeBASE-Web stop success! =======") + print "======= WeBASE-Web stop success! =======" else: - print ("======= WeBASE-Web is not running! =======") + print "======= WeBASE-Web is not running! =======" return def changeManagerConfig(): @@ -217,12 +214,10 @@ def changeManagerConfig(): return def installManager(): - print ("================================================================") - print ("============== WeBASE-Node-Manager install... ==============") + print "================================================================" + print "============== WeBASE-Node-Manager install... ==============" os.chdir(currentDir) - version = getCommProperties("webase.version") - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) - pullSourceExtract(gitComm,"webase-node-mgr") + pullSourceExtract("mgr.package.url","webase-node-mgr") changeManagerConfig() dbConnect() @@ -231,11 +226,7 @@ def installManager(): server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" - info = "n" - if sys.version_info.major == 2: - info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") - else: - info = input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") if info == "y" or info == "Y": os.chdir(script_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -244,21 +235,19 @@ def installManager(): if dbResult["status"] == 0: if_success = 'success' in dbResult["output"] if if_success: - print ("======= script init success! =======") + print "======= script init success! =======" else: - print ("======= script init fail! =======") - print (dbResult["output"]) + print "======= script init fail! =======" + print dbResult["output"] sys.exit(0) else: - print ("======= script init fail! =======") + print "======= script init fail! =======" sys.exit(0) startManager() return def startManager(): - print ("============== WeBASE-Node-Manager start... ==============") - os.chdir(currentDir) - managerPort = getCommProperties("front.port") + print "============== WeBASE-Node-Manager start... ==============" server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -266,26 +255,36 @@ def startManager(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_occupied = 'been occupied' in result["output"] - if if_occupied: - pid = get_str_btw(result["output"], "(", ")") - print ("Port {} has been occupied by other server PID({})".format(managerPort,pid)) - sys.exit(0) - if_started = 'is running' in result["output"] + if_started = 'started' in result["output"] if if_started: - pid = get_str_btw(result["output"], "(", ")") - print ("WeBASE-Node-Manager Port {} is running PID({})".format(managerPort,pid)) - sys.exit(0) + info = raw_input("WeBASE-Node-Manager进程已经存在,是否kill进程强制重启?[y/n]:") + if info == "y" or info == "Y": + doCmd("bash stop.sh") + result_start = doCmd("bash start.sh") + if result_start["status"] == 0: + if_success = 'Success' in result_start["output"] + if if_success: + print "======= WeBASE-Node-Manager start success! =======" + print "============== WeBASE-Node-Manager end... ==============" + return + else: + print "======= WeBASE-Node-Manager start fail! =======" + sys.exit(0) + else: + print "======= WeBASE-Node-Manager start fail! =======" + sys.exit(0) + else: + sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print ("======= WeBASE-Node-Manager start success! =======") + print "======= WeBASE-Node-Manager start success! =======" else: - print ("======= WeBASE-Node-Manager start fail! =======") + print "======= WeBASE-Node-Manager start fail! =======" sys.exit(0) else: - print ("======= WeBASE-Node-Manager start fail! =======") + print "======= WeBASE-Node-Manager start fail! =======" sys.exit(0) - print ("============== WeBASE-Node-Manager end... ==============") + print "============== WeBASE-Node-Manager end... ==============" return def stopManager(): @@ -298,11 +297,11 @@ def stopManager(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print ("======= WeBASE-Node-Manager stop success! =======") + print "======= WeBASE-Node-Manager stop success! =======" else: - print ("======= WeBASE-Node-Manager is not running! =======") + print "======= WeBASE-Node-Manager is not running! =======" else: - print ("======= WeBASE-Node-Manager stop fail! =======") + print "======= WeBASE-Node-Manager stop fail! =======" return def changeFrontConfig(): @@ -312,7 +311,6 @@ def changeFrontConfig(): frontPort = getCommProperties("front.port") nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") - frontDb = getCommProperties("front.h2.name") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") @@ -331,31 +329,24 @@ def changeFrontConfig(): doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) - doCmd('sed -i "s%webasefront%{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) return def installFront(): - print ("================================================================") - print ("============== WeBASE-Front install... ==============") + print "================================================================" + print "============== WeBASE-Front install... ==============" os.chdir(currentDir) - version = getCommProperties("webase.version") - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) - pullSourceExtract(gitComm,"webase-front") + pullSourceExtract("front.package.url","webase-front") changeFrontConfig() # check front db - frontDb = getCommProperties("front.h2.name") - db_dir = currentDir+"/h2" + frontDb = "webasefront" + db_dir = currentDir+"/webase-front/h2" doCmdIgnoreException("mkdir -p {}".format(db_dir)) res_file = checkFileName(db_dir,frontDb) if res_file: - info = "n" - if sys.version_info.major == 2: - info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) - else: - info = input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) if info == "y" or info == "Y": doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) @@ -366,7 +357,7 @@ def installFront(): fisco_dir = currentDir + "/nodes/127.0.0.1" sdk_dir = fisco_dir + "/sdk" if not os.path.exists(sdk_dir): - print ("======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir)) + print "======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir) sys.exit(0) server_dir = currentDir + "/webase-front" os.chdir(server_dir) @@ -376,9 +367,7 @@ def installFront(): return def startFront(): - print ("============== WeBASE-Front start... ==============") - os.chdir(currentDir) - frontPort = getCommProperties("front.port") + print "============== WeBASE-Front start... ==============" server_dir = currentDir + "/webase-front" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -386,27 +375,37 @@ def startFront(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_occupied = 'been occupied' in result["output"] - if if_occupied: - pid = get_str_btw(result["output"], "(", ")") - print ("Port {} has been occupied by other server PID({})".format(frontPort,pid)) - sys.exit(0) - if_started = 'is running' in result["output"] + if_started = 'started' in result["output"] if if_started: - pid = get_str_btw(result["output"], "(", ")") - print ("WeBASE-Front Port {} is running PID({})".format(frontPort,pid)) - sys.exit(0) + info = raw_input("WeBASE-Front进程已经存在,是否kill进程强制重启?[y/n]:") + if info == "y" or info == "Y": + doCmd("bash stop.sh") + result_start = doCmd("bash start.sh") + if result_start["status"] == 0: + if_success = 'Success' in result_start["output"] + if if_success: + print "======= WeBASE-Front start success! =======" + print "============== WeBASE-Front end... ==============" + return + else: + print "======= WeBASE-Front start fail! =======" + sys.exit(0) + else: + print "======= WeBASE-Front start fail! =======" + sys.exit(0) + else: + sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print ("======= WeBASE-Front start success! =======") + print "======= WeBASE-Front start success! =======" else: - print ("======= WeBASE-Front start fail! =======") + print "======= WeBASE-Front start fail! =======" sys.exit(0) else: - print ("======= WeBASE-Front start fail! =======") + print "======= WeBASE-Front start fail! =======" sys.exit(0) - print ("============== WeBASE-Front end... ==============") - print ("================================================================") + print "============== WeBASE-Front end... ==============" + print "================================================================" return def stopFront(): @@ -419,9 +418,9 @@ def stopFront(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print ("======= WeBASE-Front stop success! =======") + print "======= WeBASE-Front stop success! =======" else: - print ("======= WeBASE-Front is not running! =======") + print "======= WeBASE-Front is not running! =======" else: - print ("======= WeBASE-Front stop fail! =======") + print "======= WeBASE-Front stop fail! =======" return diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 0065f7d..5ecdc02 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -1,15 +1,15 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # encoding: utf-8 -from . import log as deployLog +import log as deployLog import sys -from .utils import * +from utils import * -log = deployLog.getLocalLogger() +log = deployLog.getLogger() checkDependent = ["git","openssl","curl"] def do(): - print ("================================================================"), + print "================================================================", webaseMsg = ''' _ _ ______ ___ _____ _____ | | | | | ___ \/ _ \/ ___| ___| @@ -18,9 +18,9 @@ def do(): \ /\ | __| |_/ | | | /\__/ | |___ \/ \/ \___\____/\_| |_\____/\____/ ''' - print (webaseMsg) - print ("================================================================") - print ("===================== envrionment check... =====================") + print webaseMsg + print "================================================================" + print "===================== envrionment check... =====================" installRequirements() checkNginx() checkJava() @@ -29,51 +29,50 @@ def do(): checkMgrPort() checkFrontPort() checkDbConnect() - print ("===================== envrionment ready... =====================") - print ("================================================================") + print "===================== envrionment ready... =====================" + print "================================================================" def installRequirements(): for require in checkDependent: - print ("check {}...".format(require)) + print "check {}...".format(require) hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print ("check finished sucessfully.") + print "check finished sucessfully." return def checkNginx(): - print ("check nginx...") + print "check nginx..." require = "nginx" hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print ("check finished sucessfully.") + print "check finished sucessfully." def checkJava(): - print ("check java...") + print "check java..." res_check = doCmdIgnoreException("java -version") if res_check["status"] != 0: - print (" error! java has not been installed or configured!") + print " error! java has not been installed or configured!" sys.exit(0) res_home = doCmd("echo $JAVA_HOME") if res_home["output"].strip() == "": - print (" error! JAVA_HOME has not been configured!") + print " error! JAVA_HOME has not been configured!" sys.exit(0) - print ("check finished sucessfully.") + print "check finished sucessfully." return def checkNodePort(): + print "check FISCO-BCOS node port..." if_exist_fisco = getCommProperties("if.exist.fisco") if if_exist_fisco == "yes": - # checkExistedNodePort() - return + checkExistedNodePort() elif if_exist_fisco == "no": - print ("check FISCO-BCOS node port...") checkNewNodePort() - print ("check finished sucessfully.") else: - print (" error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco)) + print " error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco) sys.exit(0) + print "check finished sucessfully." def checkExistedNodePort(): listen_ip = getCommProperties("node.listenIp") @@ -82,15 +81,15 @@ def checkExistedNodePort(): node_channelPort = int(getCommProperties("node.channelPort")) res_rpcPort = net_if_used_no_msg(listen_ip,node_rpcPort) if not res_rpcPort: - print (" error! rpc port {} is not alive. please check.".format(node_rpcPort)) + print " error! rpc port {} is not alive. please check.".format(node_rpcPort) sys.exit(0) res_p2pPort = net_if_used_no_msg(listen_ip,node_p2pPort) if not res_p2pPort: - print (" error! p2p port {} is not alive. please check.".format(node_p2pPort)) + print " error! p2p port {} is not alive. please check.".format(node_p2pPort) sys.exit(0) res_channelPort = net_if_used_no_msg(listen_ip,node_channelPort) if not res_channelPort: - print (" error! channel port {} is not alive. please check.".format(node_channelPort)) + print " error! channel port {} is not alive. please check.".format(node_channelPort) sys.exit(0) return @@ -116,44 +115,44 @@ def checkNewNodePort(): return def checkWebPort(): - print ("check WeBASE-Web port...") + print "check WeBASE-Web port..." deploy_ip = "127.0.0.1" web_port = getCommProperties("web.port") res_web = net_if_used(deploy_ip,web_port) if res_web: sys.exit(0) - print ("check finished sucessfully.") + print "check finished sucessfully." return def checkMgrPort(): - print ("check WeBASE-Node-Manager port...") + print "check WeBASE-Node-Manager port..." deploy_ip = "127.0.0.1" mgr_port = getCommProperties("mgr.port") res_mgr = net_if_used(deploy_ip,mgr_port) if res_mgr: sys.exit(0) - print ("check finished sucessfully.") + print "check finished sucessfully." return def checkFrontPort(): - print ("check WeBASE-Front port...") + print "check WeBASE-Front port..." deploy_ip = "127.0.0.1" front_port = getCommProperties("front.port") res_front = net_if_used(deploy_ip,front_port) if res_front: sys.exit(0) - print ("check finished sucessfully.") + print "check finished sucessfully." return def checkDbConnect(): - print ("check database connection...") + print "check database connection..." mysql_ip = getCommProperties("mysql.ip") mysql_port = getCommProperties("mysql.port") ifLink = do_telnet(mysql_ip,mysql_port) if not ifLink: - print ('The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port)) + print 'The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port) sys.exit(0) - print ("check finished sucessfully.") + print "check finished sucessfully." return def hasInstallServer(server): diff --git a/deploy/comm/log.py b/deploy/comm/log.py index 6de1390..8ffda8e 100644 --- a/deploy/comm/log.py +++ b/deploy/comm/log.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # encoding: utf-8 import logging, os @@ -20,7 +20,7 @@ def info(self, message): self.logger.info(message) def infoPrint(self, mesage): - print (mesage) + print mesage self.logger.info(mesage) def war(self, message): @@ -34,7 +34,7 @@ def cri(self, message): loggermap = {} -def getLocalLogger(): +def getLogger(): logPath ="./log/" logName="info.log" isExists=os.path.exists(logPath) @@ -48,5 +48,5 @@ def getLocalLogger(): return logger if __name__ == '__main__': - log = getLocalLogger() + log = getLogger() log.info("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv") \ No newline at end of file diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index 7fde026..7e83bbf 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -1,12 +1,12 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # encoding: utf-8 -from . import log as deployLog +import log as deployLog import sys import MySQLdb as mdb -from .utils import * +from utils import * -log = deployLog.getLocalLogger() +log = deployLog.getLogger() def dbConnect(): # get properties @@ -27,11 +27,7 @@ def dbConnect(): drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) if result == 1: - info = "n" - if sys.version_info.major == 2: - info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) - else: - info = input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) if info == "y" or info == "Y": log.info(drop_db) cursor.execute(drop_db) diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index d9209df..9c61bb7 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -1,29 +1,19 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # encoding: utf-8 -import os -import sys -try: - import ConfigParser -except: - try: - import configparser as ConfigParser - except: - from six.moves import configparser as ConfigParser -if sys.version_info.major == 2: - import commands -else: - import subprocess -from . import log as deployLog +import ConfigParser +import commands +import log as deployLog import socket import fcntl import struct import telnetlib +import os import platform import shutil from distutils.dir_util import copy_tree -log = deployLog.getLocalLogger() +log = deployLog.getLogger() platformStr = platform.platform() def getIpAddress(ifname): @@ -43,7 +33,7 @@ def net_if_used(ip,port): try: result=s.connect_ex((ip, int(port))) if result==0: - print (" error! port {} has been used. please check.".format(port)) + print " error! port {} has been used. please check.".format(port) return True else: return False @@ -90,10 +80,7 @@ def copytree(src, dst): def doCmd(cmd): log.info(" execute cmd start ,cmd : {}".format(cmd)) result = dict() - if sys.version_info.major == 2: - (status, output) = commands.getstatusoutput(cmd) - else: - (status, output) = subprocess.getstatusoutput(cmd) + (status, output) = commands.getstatusoutput(cmd) result["status"] = status result["output"] = output log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd,status,output)) @@ -104,10 +91,7 @@ def doCmd(cmd): def doCmdIgnoreException(cmd): log.info(" execute cmd start ,cmd : {}".format(cmd)) result = dict() - if sys.version_info.major == 2: - (status, output) = commands.getstatusoutput(cmd) - else: - (status, output) = subprocess.getstatusoutput(cmd) + (status, output) = commands.getstatusoutput(cmd) result["status"] = status result["output"] = output log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd, status, output)) @@ -125,7 +109,7 @@ def getCommProperties(paramsKey): def replaceConf(fileName,oldStr,newStr): if not os.path.isfile(fileName): - print ("{} is not a file ".format(fileName)) + print "{} is not a file ".format(fileName) return oldData ="" with open(fileName, "r") as f: @@ -139,7 +123,7 @@ def replaceConf(fileName,oldStr,newStr): def replaceConfDir(filePath,oldStr,newStr): if not os.path.isdir(filePath): - print ("{} is not a dir ".format(filePath)) + print "{} is not a dir ".format(filePath) return for root, dirs, files in os.walk(filePath): for file in files: @@ -169,37 +153,30 @@ def do_telnet(host,port): return False return True -def pullSourceExtract(gitComm,fileName): +def pullSourceExtract(urlName,fileName): + git_comm = "wget " + getCommProperties(urlName) if not os.path.exists("{}/{}.zip".format(getCurrentBaseDir(),fileName)): - print (gitComm) - os.system(gitComm) + print git_comm + os.system(git_comm) else: - info = "n" - if sys.version_info.major == 2: - info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) - else: - info = input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) if info == "y" or info == "Y": doCmd("rm -rf {}.zip".format(fileName)) doCmd("rm -rf {}".format(fileName)) - print (gitComm) - os.system(gitComm) + print git_comm + os.system(git_comm) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): - print ("{}.zip extract failed!".format(fileName)) + print "{}.zip extract failed!".format(fileName) sys.exit(0) else: - info1 = "n" - if sys.version_info.major == 2: - info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) - else: - info1 = input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) if info1 == "y" or info1 == "Y": doCmd("rm -rf {}".format(fileName)) doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): - print ("{}.zip extract failed!".format(fileName)) + print "{}.zip extract failed!".format(fileName) sys.exit(0) def checkFileName(dir,fileName): @@ -211,10 +188,6 @@ def checkFileName(dir,fileName): return True else: return False - -def get_str_btw(s, f, b): - par = s.partition(f) - return (par[2].partition(b))[0][:] if __name__ == '__main__': print(getIpAddress("eth0")) diff --git a/deploy/common.properties b/deploy/common.properties index c696b48..6270af6 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,18 +1,17 @@ [common] -# WeBASE版本(v1.0.2或以上版本) -webase.version=v1.0.2 +# 下载链接,默认不修改 +web.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-web.zip +mgr.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-node-mgr.zip +front.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-front.zip -# 节点管理子系统mysql数据库配置 +# 数据库配置 mysql.ip=localhost mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword mysql.database=webasenodemanager -# 节点前置子系统h2数据库名 -front.h2.name=webasefront - # WeBASE管理平台服务端口 web.port=5000 # 节点管理子系统服务端口 @@ -29,12 +28,12 @@ node.channelPort=20200 # 节点rpc端口 node.rpcPort=8545 -# 是否使用已有的链(yes/no) -if.exist.fisco=no +# 是否使用已有的链 +if.exist.fisco=yes # 使用已有链时需配置 -# 已有链的路径,start_all.sh脚本所在路径,路径下要存在sdk目录 -fisco.dir=/data/app/nodes/127.0.0.1 +# 已有链的路径,start_all.sh脚本所在路径 +fisco.dir=fiscoDir # 搭建新链时需配置 # FISCO-BCOS版本 diff --git a/deploy/deploy.py b/deploy/deploy.py index 218fe5f..028a613 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -1,6 +1,5 @@ -#!/usr/bin/python3 +#!/usr/bin/python # encoding: utf-8 - import sys import comm.check as commCheck import comm.build as commBuild @@ -56,16 +55,16 @@ def help(): stopFront: stop WeBASE-Front server Attention: - 1. Need to install python, jdk, mysql, MySQL-python or PyMySQL first + 1. Need to install python2.7, jdk1.8, mysql 5.6, MySQL-python first 2. Need to ensure a smooth network 3. You need to install git, wget, nginx; if it is not installed, the installation script will automatically install these components, but this may fail. ''' - print (helpMsg) + print helpMsg return def paramError(): - print ("") - print ("Param error! Please check.") + print "" + print "Param error! Please check." help() return diff --git a/quick-start/src/main/resources/application.yml b/quick-start/src/main/resources/application.yml index 10f80c1..dfa9c85 100644 --- a/quick-start/src/main/resources/application.yml +++ b/quick-start/src/main/resources/application.yml @@ -1,4 +1,4 @@ -transactionUrl: http://127.0.0.1:5002/WeBASE-Front/trans/handle +transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle groupId: 1 userAddress: "0x4f08eac5af5e77b7006d11bee94adba2f721def8" useAes: true From 4524dfea9629696bef715cf7c843665a62cfdb56 Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Fri, 23 Aug 2019 11:12:14 +0800 Subject: [PATCH 064/119] Dev (#34) * modify url * front db can be configed * modify url * support python3 * Revert "V1.1.0" --- deploy/comm/check.py | 6 +++++- deploy/common.properties | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 533c2e4..5ecdc02 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -53,7 +53,11 @@ def checkJava(): print "check java..." res_check = doCmdIgnoreException("java -version") if res_check["status"] != 0: - print " error! java is not install or configure!" + print " error! java has not been installed or configured!" + sys.exit(0) + res_home = doCmd("echo $JAVA_HOME") + if res_home["output"].strip() == "": + print " error! JAVA_HOME has not been configured!" sys.exit(0) print "check finished sucessfully." return diff --git a/deploy/common.properties b/deploy/common.properties index 958e3ba..6270af6 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,9 +1,9 @@ [common] # 下载链接,默认不修改 -web.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-web.zip -mgr.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-node-mgr.zip -front.package.url=https://www.fisco.com.cn/cdn/WeBASE/release/download/v1.0.2/webase-front.zip +web.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-web.zip +mgr.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-node-mgr.zip +front.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-front.zip # 数据库配置 mysql.ip=localhost From 46e308b6d1aadfc6ee512d627d63ee80f9273fdd Mon Sep 17 00:00:00 2001 From: Sayou1989 <32632184+Sayou1989@users.noreply.github.com> Date: Fri, 23 Aug 2019 12:27:46 +0800 Subject: [PATCH 065/119] V1.1.0 (#35) --- deploy/comm/__init__.py | 6 + deploy/comm/build.py | 211 +++++++++--------- deploy/comm/check.py | 69 +++--- deploy/comm/log.py | 8 +- deploy/comm/mysql.py | 14 +- deploy/comm/utils.py | 69 ++++-- deploy/common.properties | 19 +- deploy/deploy.py | 11 +- quick-start.md | 7 +- .../src/main/resources/application.yml | 2 +- 10 files changed, 229 insertions(+), 187 deletions(-) diff --git a/deploy/comm/__init__.py b/deploy/comm/__init__.py index e69de29..fc51ec7 100644 --- a/deploy/comm/__init__.py +++ b/deploy/comm/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +if sys.version_info.major == 3: + import pymysql + pymysql.install_as_MySQLdb() \ No newline at end of file diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 69ce098..2c4e998 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -1,23 +1,25 @@ -#!/usr/bin/python +#!/usr/bin/python3 # encoding: utf-8 import sys import os -from utils import * -from mysql import * +from .utils import * +from .mysql import dbConnect baseDir = getBaseDir() currentDir = getCurrentBaseDir() def do(): - print "===================== deploy start... =====================" + print ("===================== deploy start... =====================") installNode() installWeb() installManager() installFront() - print "===================== deploy end... =====================" - print "===================== version V1.0.2 =====================" - print "================================================================" + print ("===================== deploy end... =====================") + os.chdir(currentDir) + version = getCommProperties("webase.version") + print ("===================== version {} =====================".format(version)) + print ("================================================================") return def end(): @@ -36,8 +38,8 @@ def installNode(): node_counts = getCommProperties("node.counts") if if_exist_fisco == "no": - print "================================================================" - print "============== FISCO-BCOS install... ==============" + print ("================================================================") + print ("============== FISCO-BCOS install... ==============") # init configure file if not os.path.exists(currentDir + "/nodetemp"): doCmd('cp -f nodeconf nodetemp') @@ -53,40 +55,35 @@ def installNode(): doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - # result_build = doCmd("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - # if result_build["status"] == 0: - # if_build = 'completed' in result_build["output"] - # if not if_build: - # print "======= FISCO-BCOS build fail! =======" - # sys.exit(0) - # else: - # print "======= FISCO-BCOS build fail! =======" - # sys.exit(0) else: - info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + info = "n" + if sys.version_info.major == 2: + info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + else: + info = input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") if info == "y" or info == "Y": doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) - startNode() + startNode() def startNode(): - print "============== FISCO-BCOS start... ==============" + print ("============== FISCO-BCOS start... ==============") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") if if_exist_fisco == "no": fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/start_all.sh"): - print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) + print ("======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir)) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") os.system("bash start_all.sh") - print "============== FISCO-BCOS end... ==============" + print ("============== FISCO-BCOS end... ==============") return def stopNode(): @@ -96,7 +93,7 @@ def stopNode(): fisco_dir = currentDir + "/nodes/127.0.0.1" if not os.path.exists(fisco_dir + "/stop_all.sh"): - print "======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir) + print ("======= FISCO-BCOS dir:{} is not correct. please check! =======".format(fisco_dir)) sys.exit(0) os.chdir(fisco_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -130,17 +127,23 @@ def changeWebConfig(): return def installWeb(): - print "================================================================" - print "============== WeBASE-Web install... ==============" + print ("================================================================") + print ("============== WeBASE-Web install... ==============") os.chdir(currentDir) - pullSourceExtract("web.package.url","webase-web") + version = getCommProperties("webase.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(version) + pullSourceExtract(gitComm,"webase-web") changeWebConfig() startWeb() def startWeb(): - print "============== WeBASE-Web start... ==============" + print ("============== WeBASE-Web start... ==============") if os.path.exists("/run/nginx-webase-web.pid"): - info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + else: + info = input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") if info == "y" or info == "Y": fin = open('/run/nginx-webase-web.pid', 'r') pid = fin.read() @@ -154,14 +157,14 @@ def startWeb(): if res["status"] == 0: res2 = doCmd("sudo " + res["output"] + " -c " + nginx_config_dir) if res2["status"] == 0: - print "======= WeBASE-Web start success! =======" + print ("======= WeBASE-Web start success! =======") else: - print "======= WeBASE-Web start fail! =======" + print ("======= WeBASE-Web start fail! =======") sys.exit(0) else: - print "======= WeBASE-Web start fail! =======" + print ("======= WeBASE-Web start fail! =======") sys.exit(0) - print "============== WeBASE-Web end... ==============" + print ("============== WeBASE-Web end... ==============") return def stopWeb(): @@ -171,9 +174,9 @@ def stopWeb(): cmd = "sudo kill -QUIT {}".format(pid) os.system(cmd) doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") - print "======= WeBASE-Web stop success! =======" + print ("======= WeBASE-Web stop success! =======") else: - print "======= WeBASE-Web is not running! =======" + print ("======= WeBASE-Web is not running! =======") return def changeManagerConfig(): @@ -214,10 +217,12 @@ def changeManagerConfig(): return def installManager(): - print "================================================================" - print "============== WeBASE-Node-Manager install... ==============" + print ("================================================================") + print ("============== WeBASE-Node-Manager install... ==============") os.chdir(currentDir) - pullSourceExtract("mgr.package.url","webase-node-mgr") + version = getCommProperties("webase.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) + pullSourceExtract(gitComm,"webase-node-mgr") changeManagerConfig() dbConnect() @@ -226,7 +231,11 @@ def installManager(): server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" - info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + info = "n" + if sys.version_info.major == 2: + info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + else: + info = input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") if info == "y" or info == "Y": os.chdir(script_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -235,19 +244,21 @@ def installManager(): if dbResult["status"] == 0: if_success = 'success' in dbResult["output"] if if_success: - print "======= script init success! =======" + print ("======= script init success! =======") else: - print "======= script init fail! =======" - print dbResult["output"] + print ("======= script init fail! =======") + print (dbResult["output"]) sys.exit(0) else: - print "======= script init fail! =======" + print ("======= script init fail! =======") sys.exit(0) startManager() return def startManager(): - print "============== WeBASE-Node-Manager start... ==============" + print ("============== WeBASE-Node-Manager start... ==============") + os.chdir(currentDir) + managerPort = getCommProperties("front.port") server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -255,36 +266,26 @@ def startManager(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_started = 'started' in result["output"] + if_occupied = 'been occupied' in result["output"] + if if_occupied: + pid = get_str_btw(result["output"], "(", ")") + print ("Port {} has been occupied by other server PID({})".format(managerPort,pid)) + sys.exit(0) + if_started = 'is running' in result["output"] if if_started: - info = raw_input("WeBASE-Node-Manager进程已经存在,是否kill进程强制重启?[y/n]:") - if info == "y" or info == "Y": - doCmd("bash stop.sh") - result_start = doCmd("bash start.sh") - if result_start["status"] == 0: - if_success = 'Success' in result_start["output"] - if if_success: - print "======= WeBASE-Node-Manager start success! =======" - print "============== WeBASE-Node-Manager end... ==============" - return - else: - print "======= WeBASE-Node-Manager start fail! =======" - sys.exit(0) - else: - print "======= WeBASE-Node-Manager start fail! =======" - sys.exit(0) - else: - sys.exit(0) + pid = get_str_btw(result["output"], "(", ")") + print ("WeBASE-Node-Manager Port {} is running PID({})".format(managerPort,pid)) + sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Node-Manager start success! =======" + print ("======= WeBASE-Node-Manager start success! =======") else: - print "======= WeBASE-Node-Manager start fail! =======" + print ("======= WeBASE-Node-Manager start fail! =======") sys.exit(0) else: - print "======= WeBASE-Node-Manager start fail! =======" + print ("======= WeBASE-Node-Manager start fail! =======") sys.exit(0) - print "============== WeBASE-Node-Manager end... ==============" + print ("============== WeBASE-Node-Manager end... ==============") return def stopManager(): @@ -297,11 +298,11 @@ def stopManager(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Node-Manager stop success! =======" + print ("======= WeBASE-Node-Manager stop success! =======") else: - print "======= WeBASE-Node-Manager is not running! =======" + print ("======= WeBASE-Node-Manager is not running! =======") else: - print "======= WeBASE-Node-Manager stop fail! =======" + print ("======= WeBASE-Node-Manager stop fail! =======") return def changeFrontConfig(): @@ -311,6 +312,7 @@ def changeFrontConfig(): frontPort = getCommProperties("front.port") nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") + frontDb = getCommProperties("front.h2.name") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") @@ -329,24 +331,31 @@ def changeFrontConfig(): doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) + doCmd('sed -i "s%webasefront%{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) return def installFront(): - print "================================================================" - print "============== WeBASE-Front install... ==============" + print ("================================================================") + print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) - pullSourceExtract("front.package.url","webase-front") + version = getCommProperties("webase.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) + pullSourceExtract(gitComm,"webase-front") changeFrontConfig() # check front db - frontDb = "webasefront" - db_dir = currentDir+"/webase-front/h2" + frontDb = getCommProperties("front.h2.name") + db_dir = currentDir+"/h2" doCmdIgnoreException("mkdir -p {}".format(db_dir)) res_file = checkFileName(db_dir,frontDb) if res_file: - info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + else: + info = input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) if info == "y" or info == "Y": doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) @@ -357,7 +366,7 @@ def installFront(): fisco_dir = currentDir + "/nodes/127.0.0.1" sdk_dir = fisco_dir + "/sdk" if not os.path.exists(sdk_dir): - print "======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir) + print ("======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir)) sys.exit(0) server_dir = currentDir + "/webase-front" os.chdir(server_dir) @@ -367,7 +376,9 @@ def installFront(): return def startFront(): - print "============== WeBASE-Front start... ==============" + print ("============== WeBASE-Front start... ==============") + os.chdir(currentDir) + frontPort = getCommProperties("front.port") server_dir = currentDir + "/webase-front" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -375,37 +386,27 @@ def startFront(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_started = 'started' in result["output"] + if_occupied = 'been occupied' in result["output"] + if if_occupied: + pid = get_str_btw(result["output"], "(", ")") + print ("Port {} has been occupied by other server PID({})".format(frontPort,pid)) + sys.exit(0) + if_started = 'is running' in result["output"] if if_started: - info = raw_input("WeBASE-Front进程已经存在,是否kill进程强制重启?[y/n]:") - if info == "y" or info == "Y": - doCmd("bash stop.sh") - result_start = doCmd("bash start.sh") - if result_start["status"] == 0: - if_success = 'Success' in result_start["output"] - if if_success: - print "======= WeBASE-Front start success! =======" - print "============== WeBASE-Front end... ==============" - return - else: - print "======= WeBASE-Front start fail! =======" - sys.exit(0) - else: - print "======= WeBASE-Front start fail! =======" - sys.exit(0) - else: - sys.exit(0) + pid = get_str_btw(result["output"], "(", ")") + print ("WeBASE-Front Port {} is running PID({})".format(frontPort,pid)) + sys.exit(0) if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Front start success! =======" + print ("======= WeBASE-Front start success! =======") else: - print "======= WeBASE-Front start fail! =======" + print ("======= WeBASE-Front start fail! =======") sys.exit(0) else: - print "======= WeBASE-Front start fail! =======" + print ("======= WeBASE-Front start fail! =======") sys.exit(0) - print "============== WeBASE-Front end... ==============" - print "================================================================" + print ("============== WeBASE-Front end... ==============") + print ("================================================================") return def stopFront(): @@ -418,9 +419,9 @@ def stopFront(): if result["status"] == 0: if_success = 'Success' in result["output"] if if_success: - print "======= WeBASE-Front stop success! =======" + print ("======= WeBASE-Front stop success! =======") else: - print "======= WeBASE-Front is not running! =======" + print ("======= WeBASE-Front is not running! =======") else: - print "======= WeBASE-Front stop fail! =======" + print ("======= WeBASE-Front stop fail! =======") return diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 5ecdc02..0065f7d 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -1,15 +1,15 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 -import log as deployLog +from . import log as deployLog import sys -from utils import * +from .utils import * -log = deployLog.getLogger() +log = deployLog.getLocalLogger() checkDependent = ["git","openssl","curl"] def do(): - print "================================================================", + print ("================================================================"), webaseMsg = ''' _ _ ______ ___ _____ _____ | | | | | ___ \/ _ \/ ___| ___| @@ -18,9 +18,9 @@ def do(): \ /\ | __| |_/ | | | /\__/ | |___ \/ \/ \___\____/\_| |_\____/\____/ ''' - print webaseMsg - print "================================================================" - print "===================== envrionment check... =====================" + print (webaseMsg) + print ("================================================================") + print ("===================== envrionment check... =====================") installRequirements() checkNginx() checkJava() @@ -29,50 +29,51 @@ def do(): checkMgrPort() checkFrontPort() checkDbConnect() - print "===================== envrionment ready... =====================" - print "================================================================" + print ("===================== envrionment ready... =====================") + print ("================================================================") def installRequirements(): for require in checkDependent: - print "check {}...".format(require) + print ("check {}...".format(require)) hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkNginx(): - print "check nginx..." + print ("check nginx...") require = "nginx" hasInstall = hasInstallServer(require) if not hasInstall: installByYum(require) - print "check finished sucessfully." + print ("check finished sucessfully.") def checkJava(): - print "check java..." + print ("check java...") res_check = doCmdIgnoreException("java -version") if res_check["status"] != 0: - print " error! java has not been installed or configured!" + print (" error! java has not been installed or configured!") sys.exit(0) res_home = doCmd("echo $JAVA_HOME") if res_home["output"].strip() == "": - print " error! JAVA_HOME has not been configured!" + print (" error! JAVA_HOME has not been configured!") sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkNodePort(): - print "check FISCO-BCOS node port..." if_exist_fisco = getCommProperties("if.exist.fisco") if if_exist_fisco == "yes": - checkExistedNodePort() + # checkExistedNodePort() + return elif if_exist_fisco == "no": + print ("check FISCO-BCOS node port...") checkNewNodePort() + print ("check finished sucessfully.") else: - print " error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco) + print (" error! param if.exist.fisco must be yes or no, current is {}. please check.".format(if_exist_fisco)) sys.exit(0) - print "check finished sucessfully." def checkExistedNodePort(): listen_ip = getCommProperties("node.listenIp") @@ -81,15 +82,15 @@ def checkExistedNodePort(): node_channelPort = int(getCommProperties("node.channelPort")) res_rpcPort = net_if_used_no_msg(listen_ip,node_rpcPort) if not res_rpcPort: - print " error! rpc port {} is not alive. please check.".format(node_rpcPort) + print (" error! rpc port {} is not alive. please check.".format(node_rpcPort)) sys.exit(0) res_p2pPort = net_if_used_no_msg(listen_ip,node_p2pPort) if not res_p2pPort: - print " error! p2p port {} is not alive. please check.".format(node_p2pPort) + print (" error! p2p port {} is not alive. please check.".format(node_p2pPort)) sys.exit(0) res_channelPort = net_if_used_no_msg(listen_ip,node_channelPort) if not res_channelPort: - print " error! channel port {} is not alive. please check.".format(node_channelPort) + print (" error! channel port {} is not alive. please check.".format(node_channelPort)) sys.exit(0) return @@ -115,44 +116,44 @@ def checkNewNodePort(): return def checkWebPort(): - print "check WeBASE-Web port..." + print ("check WeBASE-Web port...") deploy_ip = "127.0.0.1" web_port = getCommProperties("web.port") res_web = net_if_used(deploy_ip,web_port) if res_web: sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkMgrPort(): - print "check WeBASE-Node-Manager port..." + print ("check WeBASE-Node-Manager port...") deploy_ip = "127.0.0.1" mgr_port = getCommProperties("mgr.port") res_mgr = net_if_used(deploy_ip,mgr_port) if res_mgr: sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkFrontPort(): - print "check WeBASE-Front port..." + print ("check WeBASE-Front port...") deploy_ip = "127.0.0.1" front_port = getCommProperties("front.port") res_front = net_if_used(deploy_ip,front_port) if res_front: sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def checkDbConnect(): - print "check database connection..." + print ("check database connection...") mysql_ip = getCommProperties("mysql.ip") mysql_port = getCommProperties("mysql.port") ifLink = do_telnet(mysql_ip,mysql_port) if not ifLink: - print 'The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port) + print ('The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port)) sys.exit(0) - print "check finished sucessfully." + print ("check finished sucessfully.") return def hasInstallServer(server): diff --git a/deploy/comm/log.py b/deploy/comm/log.py index 8ffda8e..6de1390 100644 --- a/deploy/comm/log.py +++ b/deploy/comm/log.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 import logging, os @@ -20,7 +20,7 @@ def info(self, message): self.logger.info(message) def infoPrint(self, mesage): - print mesage + print (mesage) self.logger.info(mesage) def war(self, message): @@ -34,7 +34,7 @@ def cri(self, message): loggermap = {} -def getLogger(): +def getLocalLogger(): logPath ="./log/" logName="info.log" isExists=os.path.exists(logPath) @@ -48,5 +48,5 @@ def getLogger(): return logger if __name__ == '__main__': - log = getLogger() + log = getLocalLogger() log.info("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv") \ No newline at end of file diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index 7e83bbf..7fde026 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -1,12 +1,12 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 -import log as deployLog +from . import log as deployLog import sys import MySQLdb as mdb -from utils import * +from .utils import * -log = deployLog.getLogger() +log = deployLog.getLocalLogger() def dbConnect(): # get properties @@ -27,7 +27,11 @@ def dbConnect(): drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) if result == 1: - info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + else: + info = input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) if info == "y" or info == "Y": log.info(drop_db) cursor.execute(drop_db) diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index 9c61bb7..d9209df 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -1,19 +1,29 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: utf-8 -import ConfigParser -import commands -import log as deployLog +import os +import sys +try: + import ConfigParser +except: + try: + import configparser as ConfigParser + except: + from six.moves import configparser as ConfigParser +if sys.version_info.major == 2: + import commands +else: + import subprocess +from . import log as deployLog import socket import fcntl import struct import telnetlib -import os import platform import shutil from distutils.dir_util import copy_tree -log = deployLog.getLogger() +log = deployLog.getLocalLogger() platformStr = platform.platform() def getIpAddress(ifname): @@ -33,7 +43,7 @@ def net_if_used(ip,port): try: result=s.connect_ex((ip, int(port))) if result==0: - print " error! port {} has been used. please check.".format(port) + print (" error! port {} has been used. please check.".format(port)) return True else: return False @@ -80,7 +90,10 @@ def copytree(src, dst): def doCmd(cmd): log.info(" execute cmd start ,cmd : {}".format(cmd)) result = dict() - (status, output) = commands.getstatusoutput(cmd) + if sys.version_info.major == 2: + (status, output) = commands.getstatusoutput(cmd) + else: + (status, output) = subprocess.getstatusoutput(cmd) result["status"] = status result["output"] = output log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd,status,output)) @@ -91,7 +104,10 @@ def doCmd(cmd): def doCmdIgnoreException(cmd): log.info(" execute cmd start ,cmd : {}".format(cmd)) result = dict() - (status, output) = commands.getstatusoutput(cmd) + if sys.version_info.major == 2: + (status, output) = commands.getstatusoutput(cmd) + else: + (status, output) = subprocess.getstatusoutput(cmd) result["status"] = status result["output"] = output log.info(" execute cmd end ,cmd : {},status :{} , output: {}".format(cmd, status, output)) @@ -109,7 +125,7 @@ def getCommProperties(paramsKey): def replaceConf(fileName,oldStr,newStr): if not os.path.isfile(fileName): - print "{} is not a file ".format(fileName) + print ("{} is not a file ".format(fileName)) return oldData ="" with open(fileName, "r") as f: @@ -123,7 +139,7 @@ def replaceConf(fileName,oldStr,newStr): def replaceConfDir(filePath,oldStr,newStr): if not os.path.isdir(filePath): - print "{} is not a dir ".format(filePath) + print ("{} is not a dir ".format(filePath)) return for root, dirs, files in os.walk(filePath): for file in files: @@ -153,30 +169,37 @@ def do_telnet(host,port): return False return True -def pullSourceExtract(urlName,fileName): - git_comm = "wget " + getCommProperties(urlName) +def pullSourceExtract(gitComm,fileName): if not os.path.exists("{}/{}.zip".format(getCurrentBaseDir(),fileName)): - print git_comm - os.system(git_comm) + print (gitComm) + os.system(gitComm) else: - info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + info = "n" + if sys.version_info.major == 2: + info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + else: + info = input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) if info == "y" or info == "Y": doCmd("rm -rf {}.zip".format(fileName)) doCmd("rm -rf {}".format(fileName)) - print git_comm - os.system(git_comm) + print (gitComm) + os.system(gitComm) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): - print "{}.zip extract failed!".format(fileName) + print ("{}.zip extract failed!".format(fileName)) sys.exit(0) else: - info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + info1 = "n" + if sys.version_info.major == 2: + info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + else: + info1 = input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) if info1 == "y" or info1 == "Y": doCmd("rm -rf {}".format(fileName)) doCmd("unzip -o {}.zip".format(fileName)) if not os.path.exists("{}/{}".format(getCurrentBaseDir(),fileName)): - print "{}.zip extract failed!".format(fileName) + print ("{}.zip extract failed!".format(fileName)) sys.exit(0) def checkFileName(dir,fileName): @@ -188,6 +211,10 @@ def checkFileName(dir,fileName): return True else: return False + +def get_str_btw(s, f, b): + par = s.partition(f) + return (par[2].partition(b))[0][:] if __name__ == '__main__': print(getIpAddress("eth0")) diff --git a/deploy/common.properties b/deploy/common.properties index 6270af6..250ec27 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,17 +1,18 @@ [common] -# 下载链接,默认不修改 -web.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-web.zip -mgr.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-node-mgr.zip -front.package.url=https://www.fisco.com.cn/cdn/webase/releases/download/v1.0.2/webase-front.zip +# WeBASE版本(v1.1.0或以上版本) +webase.version=v1.1.0 -# 数据库配置 +# 节点管理子系统mysql数据库配置 mysql.ip=localhost mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword mysql.database=webasenodemanager +# 节点前置子系统h2数据库名 +front.h2.name=webasefront + # WeBASE管理平台服务端口 web.port=5000 # 节点管理子系统服务端口 @@ -28,12 +29,12 @@ node.channelPort=20200 # 节点rpc端口 node.rpcPort=8545 -# 是否使用已有的链 -if.exist.fisco=yes +# 是否使用已有的链(yes/no) +if.exist.fisco=no # 使用已有链时需配置 -# 已有链的路径,start_all.sh脚本所在路径 -fisco.dir=fiscoDir +# 已有链的路径,start_all.sh脚本所在路径,路径下要存在sdk目录 +fisco.dir=/data/app/nodes/127.0.0.1 # 搭建新链时需配置 # FISCO-BCOS版本 diff --git a/deploy/deploy.py b/deploy/deploy.py index 028a613..218fe5f 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -1,5 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/python3 # encoding: utf-8 + import sys import comm.check as commCheck import comm.build as commBuild @@ -55,16 +56,16 @@ def help(): stopFront: stop WeBASE-Front server Attention: - 1. Need to install python2.7, jdk1.8, mysql 5.6, MySQL-python first + 1. Need to install python, jdk, mysql, MySQL-python or PyMySQL first 2. Need to ensure a smooth network 3. You need to install git, wget, nginx; if it is not installed, the installation script will automatically install these components, but this may fail. ''' - print helpMsg + print (helpMsg) return def paramError(): - print "" - print "Param error! Please check." + print ("") + print ("Param error! Please check.") help() return diff --git a/quick-start.md b/quick-start.md index d667a73..7fc42f4 100644 --- a/quick-start.md +++ b/quick-start.md @@ -1,7 +1,7 @@ # 使用WeBASE开发区块链应用 ## 1 部署WeBASE -搭建WeBASE, 请参考[快速部署](https://) +搭建WeBASE, 请参考[快速部署](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html) ## 2 登录WeBASE管理平台进行配置 安装WeBASE完成后,需要将节点信息添加到WeBASE平台中,这样WeBASE才可和节点进行通信。需要添加的信息包含节点信息,生成用户的私钥等。如下图所示: @@ -42,14 +42,15 @@ contract HelloWorld{ ## 4 应用层开发 ### 4.1 根据所写合约和交易api的格式,发送交易。 -请参考[交易接口](https://) +请参考[交易接口](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE-Front/interface.html#id235) 从IDE中的输出信息,拷贝合约地址,合约名,方法名等信息,同时获取用户的公钥信息,调用交易接口。 具体代码请参考 [HelloWorld范例](https://github.com/WeBankFinTech/WeBASE/tree/master/quick-start) + ### 4.2 接口调用的主要代码: * application.yml ``` -transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle +transactionUrl: http://127.0.0.1:5002/WeBASE-Front/trans/handle groupId: 1 userAddress: "0x4f08eac5af5e77b7006d11bee94adba2f721def8" useAes: true diff --git a/quick-start/src/main/resources/application.yml b/quick-start/src/main/resources/application.yml index dfa9c85..10f80c1 100644 --- a/quick-start/src/main/resources/application.yml +++ b/quick-start/src/main/resources/application.yml @@ -1,4 +1,4 @@ -transactionUrl: http://127.0.0.1:8082/webase-front/trans/handle +transactionUrl: http://127.0.0.1:5002/WeBASE-Front/trans/handle groupId: 1 userAddress: "0x4f08eac5af5e77b7006d11bee94adba2f721def8" useAes: true From 299ea1311253c4a5977fe05ab6c1d1488ad734ae Mon Sep 17 00:00:00 2001 From: Sayou1989 <32632184+Sayou1989@users.noreply.github.com> Date: Mon, 2 Sep 2019 14:36:50 +0800 Subject: [PATCH 066/119] support Travis CI (#38) * use Travis CI * use Travis CI * modify to v1.0.2 * optimize * modify to v1.1.0 --- .travis.yml | 37 +++++++++++++++++++++++++++ deploy/comm/build.py | 59 ++++++++++++++++++++++++-------------------- deploy/telnet.py | 50 +++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 4 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 .travis.yml create mode 100644 deploy/telnet.py create mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3032798 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,37 @@ +language: python +sudo: enabled +services: + - mysql +matrix: + include: + - name: "Python 2.7 on Xenial Linux" + python: 2.7 + dist: xenial + before_install: + - pip install MySQL-python + - name: "Python 3.7 on Xenial Linux" + python: 3.7 + dist: xenial + before_install: + - pip install PyMySQL +addons: + apt: + packages: + - git + - openssl + - curl + - nginx + homebrew: + packages: + - git + - openssl + - curl + - nginx +install: + - pip install -r requirements.txt +script: + - java -version + - cd deploy + - sed -i "s%dbUsername%root%g" common.properties + - sed -i "s%dbPassword%%g" common.properties + - python deploy.py installAll travis && python telnet.py diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 2c4e998..019e609 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -152,6 +152,8 @@ def startWeb(): doCmdIgnoreException("sudo rm -rf /run/nginx-webase-web.pid") else: sys.exit(0) + web_log_dir = currentDir + "/webase-web/log" + doCmd('mkdir -p {}'.format(web_log_dir)) nginx_config_dir = currentDir + "/comm/nginx.conf" res = doCmd("which nginx") if res["status"] == 0: @@ -159,10 +161,10 @@ def startWeb(): if res2["status"] == 0: print ("======= WeBASE-Web start success! =======") else: - print ("======= WeBASE-Web start fail! =======") + print ("======= WeBASE-Web start fail. Please view log file (default path:./webase-web/log/). =======") sys.exit(0) else: - print ("======= WeBASE-Web start fail! =======") + print ("======= WeBASE-Web start fail. Please view log file (default path:./log/). =======") sys.exit(0) print ("============== WeBASE-Web end... ==============") return @@ -231,27 +233,30 @@ def installManager(): server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" - info = "n" - if sys.version_info.major == 2: - info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + if len(sys.argv) == 3 and sys.argv[2] == "travis": + print ("Travis CI 不初始化数据库") else: - info = input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") - if info == "y" or info == "Y": - os.chdir(script_dir) - doCmdIgnoreException("chmod u+x *.sh") - doCmdIgnoreException("dos2unix *.sh") - dbResult = doCmd('bash webase.sh {} {}'.format(mysql_ip, mysql_port)) - if dbResult["status"] == 0: - if_success = 'success' in dbResult["output"] - if if_success: - print ("======= script init success! =======") + info = "n" + if sys.version_info.major == 2: + info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + else: + info = input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + if info == "y" or info == "Y": + os.chdir(script_dir) + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + dbResult = doCmd('bash webase.sh {} {}'.format(mysql_ip, mysql_port)) + if dbResult["status"] == 0: + if_success = 'success' in dbResult["output"] + if if_success: + print ("======= script init success! =======") + else: + print ("======= script init fail! =======") + print (dbResult["output"]) + sys.exit(0) else: - print ("======= script init fail! =======") - print (dbResult["output"]) + print ("======= script init fail. Please view log file (default path:./log/). =======") sys.exit(0) - else: - print ("======= script init fail! =======") - sys.exit(0) startManager() return @@ -280,10 +285,10 @@ def startManager(): if if_success: print ("======= WeBASE-Node-Manager start success! =======") else: - print ("======= WeBASE-Node-Manager start fail! =======") + print ("======= WeBASE-Node-Manager start fail. Please view log file (default path:./webase-node-mgr/log/). =======") sys.exit(0) else: - print ("======= WeBASE-Node-Manager start fail! =======") + print ("======= WeBASE-Node-Manager start fail. Please view log file (default path:./log/). =======") sys.exit(0) print ("============== WeBASE-Node-Manager end... ==============") return @@ -302,7 +307,7 @@ def stopManager(): else: print ("======= WeBASE-Node-Manager is not running! =======") else: - print ("======= WeBASE-Node-Manager stop fail! =======") + print ("======= WeBASE-Node-Manager stop fail. Please view log file (default path:./log/). =======") return def changeFrontConfig(): @@ -331,7 +336,7 @@ def changeFrontConfig(): doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) - doCmd('sed -i "s%webasefront%{}%g" {}/application.yml'.format(frontDb, server_dir)) + doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) return @@ -400,10 +405,10 @@ def startFront(): if if_success: print ("======= WeBASE-Front start success! =======") else: - print ("======= WeBASE-Front start fail! =======") + print ("======= WeBASE-Front start fail. Please view log file (default path:./webase-front/log/). =======") sys.exit(0) else: - print ("======= WeBASE-Front start fail! =======") + print ("======= WeBASE-Front start fail. Please view log file (default path:./log/). =======") sys.exit(0) print ("============== WeBASE-Front end... ==============") print ("================================================================") @@ -423,5 +428,5 @@ def stopFront(): else: print ("======= WeBASE-Front is not running! =======") else: - print ("======= WeBASE-Front stop fail! =======") + print ("======= WeBASE-Front stop fail. Please view log file (default path:./log/). =======") return diff --git a/deploy/telnet.py b/deploy/telnet.py new file mode 100644 index 0000000..3dfe53e --- /dev/null +++ b/deploy/telnet.py @@ -0,0 +1,50 @@ +#!/usr/bin/python3 +# encoding: utf-8 + +import sys +from comm.utils import * + +def do(): + deploy_ip = "127.0.0.1" + web_port = int(getCommProperties("web.port")) + mgr_port = int(getCommProperties("mgr.port")) + front_port = int(getCommProperties("front.port")) + + telnet_web = net_if_used_no_msg(deploy_ip,web_port) + telnet_mgr = net_if_used_no_msg(deploy_ip,mgr_port) + telnet_front = net_if_used_no_msg(deploy_ip,front_port) + print ("telnet_web:{} telnet_mgr:{} telnet_front:{}".format(telnet_web, telnet_mgr, telnet_front)) + + if telnet_web and telnet_mgr and telnet_front: + print ("deploy success.") + else: + print ("============= deploy log =============") + deploy_log = doCmdIgnoreException("cat ./log/info.log") + print (deploy_log["output"]) + if not telnet_web: + print ("============================ WeBASE-Web fail... ============================") + context = doCmdIgnoreException("cat ./webase-web/log/access.log") + print (context["output"]) + if not telnet_mgr: + print ("============================ WeBASE-Node-Manager fail... ============================") + print ("============= node-manager.out =============") + context1 = doCmdIgnoreException("cat ./webase-node-mgr/log/node-manager.out") + print (context1["output"]) + print ("============= WeBASE-Node-Manager.log =============") + context2 = doCmdIgnoreException("cat ./webase-node-mgr/log/WeBASE-Node-Manager.log") + print (context2["output"]) + if not telnet_front: + print ("============================ WeBASE-Front fail... ============================") + print ("============= front.out =============") + context1 = doCmdIgnoreException("cat ./webase-front/log/front.out") + print (context1["output"]) + print ("============= WeBASE-Front.log =============") + context2 = doCmdIgnoreException("cat ./webase-front/log/WeBASE-Front.log") + print (context2["output"]) + print ("============= web3sdk.log =============") + context3 = doCmdIgnoreException("cat ./webase-front/log/web3sdk.log") + print (context3["output"]) + raise Exception("deploy fail.") +if __name__ == '__main__': + do() + pass \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file From 2b5e0ebddfe53fa13d14db6e2bdc927167a001c8 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 11 Oct 2019 15:56:47 +0800 Subject: [PATCH 067/119] v1.2.0 --- .travis.yml | 15 +++++++++++++++ deploy/comm/build.py | 3 +++ deploy/common.properties | 8 ++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3032798..ce76036 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,21 @@ matrix: dist: xenial before_install: - pip install MySQL-python + - name: "Python 3.4 on Xenial Linux" + python: 3.4 + dist: xenial + before_install: + - pip install PyMySQL + - name: "Python 3.5 on Xenial Linux" + python: 3.5 + dist: xenial + before_install: + - pip install PyMySQL + - name: "Python 3.6 on Xenial Linux" + python: 3.6 + dist: xenial + before_install: + - pip install PyMySQL - name: "Python 3.7 on Xenial Linux" python: 3.7 dist: xenial diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 019e609..a72b4d6 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -321,8 +321,10 @@ def changeFrontConfig(): if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") + node_dir = getCommProperties("node.dir") if if_exist_fisco == "no": fisco_dir = currentDir + "/nodes/127.0.0.1" + node_dir = currentDir + "/nodes/127.0.0.1/node0" # init file server_dir = currentDir + "/webase-front/conf" @@ -338,6 +340,7 @@ def changeFrontConfig(): doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) + doCmd('sed -i "s%nodeCertPath: /fisco/nodes/127.0.0.1/node0%nodeCertPath: {}%g" {}/application.yml'.format(node_dir, server_dir)) return diff --git a/deploy/common.properties b/deploy/common.properties index 250ec27..d82e58d 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,7 @@ [common] # WeBASE版本(v1.1.0或以上版本) -webase.version=v1.1.0 +webase.version=v1.2.0 # 节点管理子系统mysql数据库配置 mysql.ip=localhost @@ -33,8 +33,12 @@ node.rpcPort=8545 if.exist.fisco=no # 使用已有链时需配置 -# 已有链的路径,start_all.sh脚本所在路径,路径下要存在sdk目录 +# 已有链的路径,start_all.sh脚本所在路径 +# 路径下要存在sdk目录,里面存放sdk证书(ca.crt、node.crt和node.key) fisco.dir=/data/app/nodes/127.0.0.1 +# 前置所连接节点的绝对路径 +# 路径下有conf目录,里面存放节点证书(ca.crt、node.crt和node.key) +node.dir=/data/app/nodes/127.0.0.1/node0 # 搭建新链时需配置 # FISCO-BCOS版本 From 959d2045c12b252740ec3caba28e86cf0b635610 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Mon, 14 Oct 2019 15:53:46 +0800 Subject: [PATCH 068/119] nodeCertPath modify to nodePath --- deploy/comm/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index a72b4d6..2075649 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -340,7 +340,7 @@ def changeFrontConfig(): doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) - doCmd('sed -i "s%nodeCertPath: /fisco/nodes/127.0.0.1/node0%nodeCertPath: {}%g" {}/application.yml'.format(node_dir, server_dir)) + doCmd('sed -i "s%nodePath: /fisco/nodes/127.0.0.1/node0%nodePath: {}%g" {}/application.yml'.format(node_dir, server_dir)) return From 5ae96198d1264ec96f73e40144f7afd1c8cbd7d9 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 11:28:43 +0800 Subject: [PATCH 069/119] Create README-en.md add English doc --- README-en.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README-en.md diff --git a/README-en.md b/README-en.md new file mode 100644 index 0000000..aa49c79 --- /dev/null +++ b/README-en.md @@ -0,0 +1 @@ +#What is WeBASE From a1a3127f4263120725b6ca5c0e62747ee119313c Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 11:54:38 +0800 Subject: [PATCH 070/119] Update README-en.md translate int English document. --- README-en.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/README-en.md b/README-en.md index aa49c79..6a1e343 100644 --- a/README-en.md +++ b/README-en.md @@ -1 +1,35 @@ -#What is WeBASE +# What's WeBASE? + +WeBASE (WeBank Blockchain Application Software Extension) is a set of general components building between blockchain application and FISCO-BCOS Nodes. Each module is designed around transaction, contract, key management, data and visual management. Developers can choose subsystems for deployment according to business needs. WeBASE shields the complexity of the bottom layer of the blockchain, reduces the threshold of developers, and greatly improves the development efficiency of the blockchain application. it includes subsystems such as node front, node management, transaction link, data export, web management platform, etc.For details, please refer to [WeBASE Online documentation](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) + +WeBASE standardizes the application and development of blockchain. After building the FISCO BCOS nodes, only five step standard process is needed for the application and development of blockchain. For the development process, please refer to [using webase to develop blockchain application](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) + +## Subsystem introduction +* **Node Front service [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** +It integrates web3jsdk and provides restful interface. The client can interact with the node in the form of HTTP. The built-in memory database collects the health data of the node. Built in Web console to realize the visual operation of nodes. + +* **Node management service [WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** +Handle all web requests on the front-end page, manage the status of each node, manage all smart contracts on the chain, make statistics and Analysis on the data of the blockchain, audit abnormal transactions, private key management, etc. + +* **WeBASE management platform [WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** +Visual operation platform, based on which node information can be viewed and smart contracts can be developed. + +* **Transcation service [WeBASE-Transcation](https://github.com/WeBankFinTech/WeBASE-Transcation)** +Receive transaction request, cache transaction to database and asynchronously chain up, which can greatly improve throughput and solve the TPS bottleneck of blockchain. + +* **Private key Hosting and cloud signature service [WeBASE-Sign](https://github.com/WeBankFinTech/WeBASE-Sign)** +Hosting user private key, providing cloud signature. + +* **Data export code generation tool [WeBASE-Codegen-Monkey](https://github.com/WeBankFinTech/WeBASE-Codegen-Monkey)** +The code generation tool can generate the core code of data export through configuration. + +* **Data export service [WeBASE-Collect-Bee](https://github.com/WeBankFinTech/WeBASE-Collect-Bee)** +Export the basic data on the blockchain, such as the current block height, total transaction volume, etc. export the business data of the contract on the blockchain, including the event, constructor, contract address, execution function information, etc. through the configuration of the smart contract. + +## Contribution +Please read our [contribution document](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/CONTRIBUTING.html) to learn how to contribute your code and submit your contribution. + +I hope that with your participation, webase will get better and better! + +## Community +Contact us:webase@webank.com From 5f67a5e31668548c1c2deb6b33cfb151952a8557 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 11:56:57 +0800 Subject: [PATCH 071/119] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a6f6272..e1bd0a6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[中文](README.md)|[English](README-en.md) + # 什么是WeBASE WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。详细介绍请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) From ec6681cf5a4ff4ea52623a9898c854c449907d5f Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 11:59:34 +0800 Subject: [PATCH 072/119] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1bd0a6..b5c5e69 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[中文](README.md)|[English](README-en.md) +中文|[English](README-en.md) # 什么是WeBASE From f4be91ddf17374344004fd2e138f7c3af1638e17 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 12:07:00 +0800 Subject: [PATCH 073/119] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b5c5e69..15ec283 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ 中文|[English](README-en.md) +![image](https://webasedoc.readthedocs.io/zh_CN/latest/_images/logo.jpg) + # 什么是WeBASE WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。详细介绍请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) From ccd94cea2cde3d4ba03b733daf4cba66808d3245 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 12:08:23 +0800 Subject: [PATCH 074/119] Update README-en.md --- README-en.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README-en.md b/README-en.md index 6a1e343..3a68f87 100644 --- a/README-en.md +++ b/README-en.md @@ -1,3 +1,5 @@ +![image](https://webasedoc.readthedocs.io/zh_CN/latest/_images/logo.jpg) + # What's WeBASE? WeBASE (WeBank Blockchain Application Software Extension) is a set of general components building between blockchain application and FISCO-BCOS Nodes. Each module is designed around transaction, contract, key management, data and visual management. Developers can choose subsystems for deployment according to business needs. WeBASE shields the complexity of the bottom layer of the blockchain, reduces the threshold of developers, and greatly improves the development efficiency of the blockchain application. it includes subsystems such as node front, node management, transaction link, data export, web management platform, etc.For details, please refer to [WeBASE Online documentation](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) From dfbea98941a250c1de72a2d45af8ad599b63c99d Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 12:10:37 +0800 Subject: [PATCH 075/119] Update README-en.md --- README-en.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README-en.md b/README-en.md index 3a68f87..e5a83dd 100644 --- a/README-en.md +++ b/README-en.md @@ -1,3 +1,5 @@ +[中文](README.md)|English + ![image](https://webasedoc.readthedocs.io/zh_CN/latest/_images/logo.jpg) # What's WeBASE? From ff1e13854fa6c7d4e20fe3fc8793a2fefd84d654 Mon Sep 17 00:00:00 2001 From: burningcpu <103679201@qq.com> Date: Tue, 5 Nov 2019 12:11:12 +0800 Subject: [PATCH 076/119] Update README-en.md --- README-en.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-en.md b/README-en.md index e5a83dd..8faf831 100644 --- a/README-en.md +++ b/README-en.md @@ -33,7 +33,7 @@ Export the basic data on the blockchain, such as the current block height, total ## Contribution Please read our [contribution document](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/CONTRIBUTING.html) to learn how to contribute your code and submit your contribution. -I hope that with your participation, webase will get better and better! +I hope that with your participation, WeBASE will get better and better! ## Community -Contact us:webase@webank.com +Contact us: webase@webank.com From 64bde3fadc0ac53e67794f081b81de7764f785af Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Tue, 19 Nov 2019 11:15:14 +0800 Subject: [PATCH 077/119] Create README-en.md --- deploy/README-en.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 deploy/README-en.md diff --git a/deploy/README-en.md b/deploy/README-en.md new file mode 100644 index 0000000..80e5b9b --- /dev/null +++ b/deploy/README-en.md @@ -0,0 +1,12 @@ +# Quick build +## Introduction + +One-click deployment quickly builds A WeBASE management desk environment. Includes node (FISCO-BCOS 2.0), node front subsystem (WeBASE-Front), node management subsystem (WeBASE-Node-Manager), management platform (WeBASE-Web). Among them, the configuration of the node is optional, you can choose to use the existing chain or build a new chain. For more information, check out the online documentation for one-click deployment (https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html). + +## A description of the contribution +Read our contribution documentation to learn how to contribute code and submit your contribution. + +Hopefully with your participation, WeBASE will get better and better! + +## The community +Contact us: webase@webank.com From b0685a7e215c58c2a5c9a671bde3dbbdaa4d8826 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 19 Nov 2019 20:00:53 +0800 Subject: [PATCH 078/119] add startAll --- deploy/comm/build.py | 7 +++++++ deploy/comm/check.py | 5 +++++ deploy/common.properties | 2 +- deploy/deploy.py | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 2075649..ad0930d 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -22,6 +22,13 @@ def do(): print ("================================================================") return +def start(): + startNode() + startWeb() + startManager() + startFront() + return + def end(): stopNode() stopWeb() diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 0065f7d..8f356bb 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -32,6 +32,11 @@ def do(): print ("===================== envrionment ready... =====================") print ("================================================================") +def checkPort(): + checkWebPort() + checkMgrPort() + checkFrontPort() + def installRequirements(): for require in checkDependent: print ("check {}...".format(require)) diff --git a/deploy/common.properties b/deploy/common.properties index d82e58d..c665981 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,7 @@ [common] # WeBASE版本(v1.1.0或以上版本) -webase.version=v1.2.0 +webase.version=v1.2.1 # 节点管理子系统mysql数据库配置 mysql.ip=localhost diff --git a/deploy/deploy.py b/deploy/deploy.py index 218fe5f..892b2cd 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -12,6 +12,9 @@ def do(): if "installAll" == param: commCheck.do() commBuild.do() + if "startAll" == param: + commCheck.checkPort() + commBuild.start() elif "stopAll" == param: commBuild.end() elif "startNode" == param: @@ -45,6 +48,7 @@ def help(): Parameter: check: check the environment installAll: check the environment, deploy all server + startAll: check server port, start all server stopAll: stop all server startNode: start FISCO-BCOS nodes stopNode: stop FISCO-BCOS nodes From cd2aaa925ae5b641519c3629f2a7ee56172447bb Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Wed, 20 Nov 2019 09:32:19 +0800 Subject: [PATCH 079/119] revert version --- deploy/common.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/common.properties b/deploy/common.properties index c665981..d82e58d 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,7 @@ [common] # WeBASE版本(v1.1.0或以上版本) -webase.version=v1.2.1 +webase.version=v1.2.0 # 节点管理子系统mysql数据库配置 mysql.ip=localhost From 1244127fc5ecd2fc10092e097540fa67862fb0d4 Mon Sep 17 00:00:00 2001 From: "42059349@qq.com" <42059349@qq.com> Date: Wed, 20 Nov 2019 10:15:20 +0800 Subject: [PATCH 080/119] english version --- deploy/comm/build.py | 18 +++++++++--------- deploy/comm/mysql.py | 4 ++-- deploy/comm/utils.py | 8 ++++---- deploy/common.properties | 38 +++++++++++++++++++------------------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index ad0930d..6e37ea1 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -65,9 +65,9 @@ def installNode(): else: info = "n" if sys.version_info.major == 2: - info = raw_input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + info = raw_input("FISCO-BCOS node directory “nodes” already exists. Reinstall or not?[y/n]:") else: - info = input("FISCO-BCOS节点目录nodes已经存在。是否重新安装?[y/n]:") + info = input("FISCO-BCOS node directory “nodes” already exists. Reinstall or not?[y/n]:") if info == "y" or info == "Y": doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") @@ -148,9 +148,9 @@ def startWeb(): if os.path.exists("/run/nginx-webase-web.pid"): info = "n" if sys.version_info.major == 2: - info = raw_input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + info = raw_input("WeBASE-Web Process already exists. Kill process to force restart?[y/n]:") else: - info = input("WeBASE-Web进程已经存在,是否kill进程强制重启?[y/n]:") + info = input("WeBASE-Web Process already exists. Kill process to force restart?[y/n]:") if info == "y" or info == "Y": fin = open('/run/nginx-webase-web.pid', 'r') pid = fin.read() @@ -241,13 +241,13 @@ def installManager(): script_dir = server_dir + "/script" if len(sys.argv) == 3 and sys.argv[2] == "travis": - print ("Travis CI 不初始化数据库") + print ("Travis CI do not initialize database") else: info = "n" if sys.version_info.major == 2: - info = raw_input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + info = raw_input("Whether to initialize the data (the first deployment or rebuilding of the library needs to be performed)?[y/n]:") else: - info = input("是否初始化数据(首次部署或重建库需执行)?[y/n]:") + info = input("Whether to initialize the data (the first deployment or rebuilding of the library needs to be performed)?[y/n]:") if info == "y" or info == "Y": os.chdir(script_dir) doCmdIgnoreException("chmod u+x *.sh") @@ -368,9 +368,9 @@ def installFront(): if res_file: info = "n" if sys.version_info.major == 2: - info = raw_input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + info = raw_input("WeBASE-Front database {} already exists, delete rebuild or not?[y/n]:".format(frontDb)) else: - info = input("WeBASE-Front数据库{}已经存在,是否删除重建?[y/n]:".format(frontDb)) + info = input("WeBASE-Front database {} already exists, delete rebuild or not?[y/n]:".format(frontDb)) if info == "y" or info == "Y": doCmdIgnoreException("rm -rf {}/{}.*".format(db_dir,frontDb)) diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index 7fde026..f36a7d8 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -29,9 +29,9 @@ def dbConnect(): if result == 1: info = "n" if sys.version_info.major == 2: - info = raw_input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = raw_input("WeBASE-Node-Manager database {} already exists, delete rebuild or not?[y/n]:".format(mysql_database)) else: - info = input("WeBASE-Node-Manager数据库{}已经存在,是否删除重建?[y/n]:".format(mysql_database)) + info = input("WeBASE-Node-Manager database {} already exists, delete rebuild or not?[y/n]:".format(mysql_database)) if info == "y" or info == "Y": log.info(drop_db) cursor.execute(drop_db) diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index d9209df..41b43b1 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -176,9 +176,9 @@ def pullSourceExtract(gitComm,fileName): else: info = "n" if sys.version_info.major == 2: - info = raw_input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + info = raw_input("{}.zip already exists. Download again or not?[y/n]:".format(fileName)) else: - info = input("{}.zip编译包已经存在。是否重新下载?[y/n]:".format(fileName)) + info = input("{}.zip already exists. Download again or not?[y/n]:".format(fileName)) if info == "y" or info == "Y": doCmd("rm -rf {}.zip".format(fileName)) doCmd("rm -rf {}".format(fileName)) @@ -192,9 +192,9 @@ def pullSourceExtract(gitComm,fileName): else: info1 = "n" if sys.version_info.major == 2: - info1 = raw_input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + info1 = raw_input("{}.zip already exists. Download again or not?[y/n]:".format(fileName)) else: - info1 = input("{}.zip编译包已经解压。是否重新解压?[y/n]:".format(fileName)) + info1 = input("{}.zip already exists. Download again or not?[y/n]:".format(fileName)) if info1 == "y" or info1 == "Y": doCmd("rm -rf {}".format(fileName)) doCmd("unzip -o {}.zip".format(fileName)) diff --git a/deploy/common.properties b/deploy/common.properties index d82e58d..8ee340f 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,47 +1,47 @@ [common] -# WeBASE版本(v1.1.0或以上版本) +# Webase Version (V1.1.0 or above) webase.version=v1.2.0 -# 节点管理子系统mysql数据库配置 +# Mysql database configuration of WeBASE-NodeManager mysql.ip=localhost mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword mysql.database=webasenodemanager -# 节点前置子系统h2数据库名 +# H2 database name of WeBASE-Front front.h2.name=webasefront -# WeBASE管理平台服务端口 +# WeBASE-Web service port web.port=5000 -# 节点管理子系统服务端口 +# WeBASE-NodeManager service port mgr.port=5001 -# 节点前置子系统端口 +# WeBASE-Front service port front.port=5002 -# 节点监听Ip +# Node listening IP node.listenIp=127.0.0.1 -# 节点p2p端口 +# Node p2p service port node.p2pPort=30300 -# 节点链上链下端口 +# Node channel service port node.channelPort=20200 -# 节点rpc端口 +# Node rpc service port node.rpcPort=8545 -# 是否使用已有的链(yes/no) +# Use existing chain or not(yes/no) if.exist.fisco=no -# 使用已有链时需配置 -# 已有链的路径,start_all.sh脚本所在路径 -# 路径下要存在sdk目录,里面存放sdk证书(ca.crt、node.crt和node.key) +# Configuration is required when using the existing chain +# The path of the existing chain, the path of the start_all.sh script +# Under the path, there should be a SDK directory where the SDK certificates (ca.crt, node.crt and node. Key) are stored fisco.dir=/data/app/nodes/127.0.0.1 -# 前置所连接节点的绝对路径 -# 路径下有conf目录,里面存放节点证书(ca.crt、node.crt和node.key) +# Absolute path of the connected node in WeBASE-Front +# Under the path, there is a conf directory where node certificates (ca.crt, node.crt and node. Key) are stored node.dir=/data/app/nodes/127.0.0.1/node0 -# 搭建新链时需配置 -# FISCO-BCOS版本 +# Configuration required when building a new chain +# Fisco-bcos version fisco.version=2.0.0 -# 搭建节点个数(默认两个) +# Number of building nodes (two by default) node.counts=nodeCounts \ No newline at end of file From bad7d9deb323df8f71dc1b2663f60c7f972887e1 Mon Sep 17 00:00:00 2001 From: "42059349@qq.com" <42059349@qq.com> Date: Wed, 20 Nov 2019 15:21:17 +0800 Subject: [PATCH 081/119] english version --- deploy/comm/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/comm/utils.py b/deploy/comm/utils.py index 41b43b1..960c232 100644 --- a/deploy/comm/utils.py +++ b/deploy/comm/utils.py @@ -192,9 +192,9 @@ def pullSourceExtract(gitComm,fileName): else: info1 = "n" if sys.version_info.major == 2: - info1 = raw_input("{}.zip already exists. Download again or not?[y/n]:".format(fileName)) + info1 = raw_input("{}.zip has been unzipped. Whether to re-unzip?[y/n]:".format(fileName)) else: - info1 = input("{}.zip already exists. Download again or not?[y/n]:".format(fileName)) + info1 = input("{}.zip has been unzipped. Whether to re-unzip?[y/n]:".format(fileName)) if info1 == "y" or info1 == "Y": doCmd("rm -rf {}".format(fileName)) doCmd("unzip -o {}.zip".format(fileName)) From d5d5e267891ab8924dd11f150aa216dcfe3cd6df Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Thu, 21 Nov 2019 11:46:32 +0800 Subject: [PATCH 082/119] optimize --- deploy/deploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/deploy.py b/deploy/deploy.py index 892b2cd..5ad747b 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -12,7 +12,7 @@ def do(): if "installAll" == param: commCheck.do() commBuild.do() - if "startAll" == param: + elif "startAll" == param: commCheck.checkPort() commBuild.start() elif "stopAll" == param: From 29a1152e30ffb18a8b03a90c77bc2920026f583d Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 28 Nov 2019 11:02:14 +0800 Subject: [PATCH 083/119] update v1.2.1 --- deploy/common.properties | 2 +- quick-start/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index 8ee340f..86a1a84 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,7 @@ [common] # Webase Version (V1.1.0 or above) -webase.version=v1.2.0 +webase.version=v1.2.1 # Mysql database configuration of WeBASE-NodeManager mysql.ip=localhost diff --git a/quick-start/build.gradle b/quick-start/build.gradle index c279144..d0f69f7 100644 --- a/quick-start/build.gradle +++ b/quick-start/build.gradle @@ -39,7 +39,7 @@ List test = [ dependencies { compile springboot // compile "org.apache.commons:commons-lang3:3.8.1" - compile "com.alibaba:fastjson:1.2.54" + compile "com.alibaba:fastjson:1.2.60" compileOnly lombok From 74bdbab0b0e9a83479c7b78a3af1ea25ef8ea962 Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 13 Dec 2019 11:00:54 +0800 Subject: [PATCH 084/119] add encrypt type --- deploy/build_chain.sh | 1175 -------------------------------------- deploy/comm/build.py | 77 ++- deploy/common.properties | 7 +- 3 files changed, 65 insertions(+), 1194 deletions(-) delete mode 100644 deploy/build_chain.sh diff --git a/deploy/build_chain.sh b/deploy/build_chain.sh deleted file mode 100644 index cfac629..0000000 --- a/deploy/build_chain.sh +++ /dev/null @@ -1,1175 +0,0 @@ -#!/bin/bash - -set -e - -# default value -ca_file= #CA key -node_num=1 -ip_file= -ip_param= -use_ip_param= -agency_array= -group_array= -ip_array= -output_dir=nodes -port_start=(30300 20200 8545) -state_type=storage -storage_type=rocksdb -conf_path="conf" -bin_path= -make_tar= -debug_log="false" -log_level="info" -logfile=${PWD}/build.log -listen_ip="127.0.0.1" -bcos_bin_name=fisco-bcos -guomi_mode= -docker_mode= -gm_conf_path="gmconf/" -current_dir=$(pwd) -consensus_type="pbft" -TASSL_CMD="${HOME}"/.tassl -enable_parallel=true -auto_flush="true" -# trans timestamp from seconds to milliseconds -timestamp=$(($(date '+%s')*1000)) -chain_id=1 -compatibility_version="" -default_version="2.0.0" -macOS="" -download_timeout=360 - -help() { - echo $1 - cat << EOF -Usage: - -l [Required] "ip1:nodeNum1,ip2:nodeNum2" e.g:"192.168.0.1:2,192.168.0.2:3" - -f [Optional] split by line, every line should be "ip:nodeNum agencyName groupList". eg "127.0.0.1:4 agency1 1,2" - -e Default download fisco-bcos from GitHub. If set -e, use the binary at the specified location - -o Default ./nodes/ - -p Default 30300,20200,8545 means p2p_port start from 30300, channel_port from 20200, jsonrpc_port from 8545 - -i Default 127.0.0.1. If set -i, listen 0.0.0.0 - -v Default get version from https://github.com/FISCO-BCOS/FISCO-BCOS/releases. If set use specificd version binary - -s Default rocksdb. Options can be rocksdb / mysql / external, rocksdb is recommended - -d Default off. If set -d, build with docker - -c Default PBFT. If set -c, use Raft - -m Default storageState. if set -m, use mpt state - -C Default 1. Can set uint. - -g Default no - -z Default no - -t Default auto generate - -T Default off. If set -T, enable debug log - -F Default on. If set -F, disable log auto flush - -h Help -e.g - $0 -l "127.0.0.1:4" -EOF - -exit 0 -} - -LOG_WARN() -{ - local content=${1} - echo -e "\033[31m[WARN] ${content}\033[0m" -} - -LOG_INFO() -{ - local content=${1} - echo -e "\033[32m[INFO] ${content}\033[0m" -} - -exit_with_clean() -{ - local content=${1} - echo -e "\033[31m[ERROR] ${content}\033[0m" - if [ -d "${output_dir}" ];then - rm -rf ${output_dir} - fi - exit 1 -} - -parse_params() -{ -while getopts "f:l:o:p:e:t:v:s:C:iczhgmTFd" option;do - case $option in - f) ip_file=$OPTARG - use_ip_param="false" - ;; - l) ip_param=$OPTARG - use_ip_param="true" - ;; - o) output_dir=$OPTARG;; - i) listen_ip="0.0.0.0";; - v) compatibility_version="$OPTARG";; - p) port_start=(${OPTARG//,/ }) - if [ ${#port_start[@]} -ne 3 ];then LOG_WARN "start port error. e.g: 30300,20200,8545" && exit 1;fi - ;; - e) bin_path=$OPTARG;; - m) state_type=mpt;; - s) storage_type=$OPTARG - if [ -z "${storage_type}" ];then - LOG_WARN "${storage_type} is not supported storage." - exit 1; - fi - ;; - t) CertConfig=$OPTARG;; - c) consensus_type="raft";; - C) chain_id=$OPTARG - if [ -z $(grep '^[[:digit:]]*$' <<< "${chain_id}") ];then - LOG_WARN "${chain_id} is not a positive integer." - exit 1; - fi - ;; - T) debug_log="true" - log_level="debug" - ;; - F) auto_flush="false";; - z) make_tar="yes";; - g) guomi_mode="yes";; - d) docker_mode="yes" - [ ! -z "${macOS}" ] && LOG_WARN "Docker desktop of macOS can't support docker mode of FISCO BCOS!" && exit 1;; - h) help;; - esac -done -} - -print_result() -{ -echo "================================================================" -LOG_INFO "Execute the following command to get FISCO-BCOS console" -echo " bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/download_console.sh)" -echo "================================================================" -[ -z ${docker_mode} ] && LOG_INFO "FISCO-BCOS Path : $bin_path" -[ ! -z ${docker_mode} ] && LOG_INFO "Docker tag : latest" -[ ! -z $ip_file ] && LOG_INFO "IP List File : $ip_file" -# [ ! -z $ip_file ] && LOG_INFO -e "Agencies/groups : ${#agency_array[@]}/${#groups[@]}" -LOG_INFO "Start Port : ${port_start[*]}" -LOG_INFO "Server IP : ${ip_array[*]}" -LOG_INFO "State Type : ${state_type}" -LOG_INFO "RPC listen IP : ${listen_ip}" -LOG_INFO "Output Dir : ${output_dir}" -LOG_INFO "CA Key Path : $ca_file" -[ ! -z $guomi_mode ] && LOG_INFO "Guomi mode : $guomi_mode" -echo "================================================================" -LOG_INFO "All completed. Files in ${output_dir}" -} - -check_env() { - [ ! -z "$(openssl version | grep 1.0.2)" ] || [ ! -z "$(openssl version | grep 1.1)" ] || [ ! -z "$(openssl version | grep reSSL)" ] || { - echo "please install openssl!" - #echo "download openssl from https://www.openssl.org." - echo "use \"openssl version\" command to check." - exit 1 - } - if [ ! -z "$(openssl version | grep reSSL)" ];then - export PATH="/usr/local/opt/openssl/bin:$PATH" - fi - if [ "$(uname)" == "Darwin" ];then - macOS="macOS" - fi - -} - -# TASSL env -check_and_install_tassl() -{ - if [ ! -f "${HOME}/.tassl" ];then - curl -LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/tassl.tar.gz - LOG_INFO "Downloading tassl binary ..." - tar zxvf tassl.tar.gz - chmod u+x tassl - mv tassl ${HOME}/.tassl - fi -} - -getname() { - local name="$1" - if [ -z "$name" ]; then - return 0 - fi - [[ "$name" =~ ^.*/$ ]] && { - name="${name%/*}" - } - name="${name##*/}" - echo "$name" -} - -check_name() { - local name="$1" - local value="$2" - [[ "$value" =~ ^[a-zA-Z0-9._-]+$ ]] || { - exit_with_clean "$name name [$value] invalid, it should match regex: ^[a-zA-Z0-9._-]+\$" - } -} - -file_must_exists() { - if [ ! -f "$1" ]; then - exit_with_clean "$1 file does not exist, please check!" - fi -} - -dir_must_exists() { - if [ ! -d "$1" ]; then - exit_with_clean "$1 DIR does not exist, please check!" - fi -} - -dir_must_not_exists() { - if [ -e "$1" ]; then - LOG_WARN "$1 DIR exists, please clean old DIR!" - exit 1 - fi -} - -gen_chain_cert() { - local path="${1}" - name=$(getname "$path") - echo "$path --- $name" - dir_must_not_exists "$path" - check_name chain "$name" - - chaindir=$path - mkdir -p $chaindir - openssl genrsa -out $chaindir/ca.key 2048 - openssl req -new -x509 -days 3650 -subj "/CN=$name/O=fisco-bcos/OU=chain" -key $chaindir/ca.key -out $chaindir/ca.crt - mv cert.cnf $chaindir -} - -gen_agency_cert() { - local chain="${1}" - local agencypath="${2}" - name=$(getname "$agencypath") - - dir_must_exists "$chain" - file_must_exists "$chain/ca.key" - check_name agency "$name" - agencydir=$agencypath - dir_must_not_exists "$agencydir" - mkdir -p $agencydir - - openssl genrsa -out $agencydir/agency.key 2048 - openssl req -new -sha256 -subj "/CN=$name/O=fisco-bcos/OU=agency" -key $agencydir/agency.key -config $chain/cert.cnf -out $agencydir/agency.csr - openssl x509 -req -days 3650 -sha256 -CA $chain/ca.crt -CAkey $chain/ca.key -CAcreateserial\ - -in $agencydir/agency.csr -out $agencydir/agency.crt -extensions v4_req -extfile $chain/cert.cnf - - cp $chain/ca.crt $chain/cert.cnf $agencydir/ - rm -f $agencydir/agency.csr - - echo "build $name agency cert successful!" -} - -gen_cert_secp256k1() { - capath="$1" - certpath="$2" - name="$3" - type="$4" - openssl ecparam -out $certpath/${type}.param -name secp256k1 - openssl genpkey -paramfile $certpath/${type}.param -out $certpath/${type}.key - openssl pkey -in $certpath/${type}.key -pubout -out $certpath/${type}.pubkey - openssl req -new -sha256 -subj "/CN=${name}/O=fisco-bcos/OU=${type}" -key $certpath/${type}.key -config $capath/cert.cnf -out $certpath/${type}.csr - openssl x509 -req -days 3650 -sha256 -in $certpath/${type}.csr -CAkey $capath/agency.key -CA $capath/agency.crt\ - -force_pubkey $certpath/${type}.pubkey -out $certpath/${type}.crt -CAcreateserial -extensions v3_req -extfile $capath/cert.cnf - openssl ec -in $certpath/${type}.key -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32 | cat >$certpath/${type}.private - rm -f $certpath/${type}.csr -} - -gen_node_cert() { - if [ "" == "$(openssl ecparam -list_curves 2>&1 | grep secp256k1)" ]; then - exit_with_clean "openssl don't support secp256k1, please upgrade openssl!" - fi - - agpath="${1}" - agency=$(getname "$agpath") - ndpath="${2}" - node=$(getname "$ndpath") - dir_must_exists "$agpath" - file_must_exists "$agpath/agency.key" - check_name agency "$agency" - dir_must_not_exists "$ndpath" - check_name node "$node" - - mkdir -p $ndpath - - gen_cert_secp256k1 "$agpath" "$ndpath" "$node" node - #nodeid is pubkey - openssl ec -in $ndpath/node.key -text -noout | sed -n '7,11p' | tr -d ": \n" | awk '{print substr($0,3);}' | cat >$ndpath/node.nodeid - # openssl x509 -serial -noout -in $ndpath/node.crt | awk -F= '{print $2}' | cat >$ndpath/node.serial - cp $agpath/ca.crt $agpath/agency.crt $ndpath - - cd $ndpath - - echo "build $node node cert successful!" -} - -generate_gmsm2_param() -{ - local output=$1 - cat << EOF > ${output} ------BEGIN EC PARAMETERS----- -BggqgRzPVQGCLQ== ------END EC PARAMETERS----- - -EOF -} - -gen_chain_cert_gm() { - local path="${1}" - name=$(getname "$path") - echo "$path --- $name" - dir_must_not_exists "$path" - check_name chain "$name" - - chaindir=$path - mkdir -p $chaindir - - generate_gmsm2_param "gmsm2.param" - $TASSL_CMD genpkey -paramfile gmsm2.param -out $chaindir/gmca.key - $TASSL_CMD req -config gmcert.cnf -x509 -days 3650 -subj "/CN=$name/O=fiscobcos/OU=chain" -key $chaindir/gmca.key -extensions v3_ca -out $chaindir/gmca.crt - - cp gmcert.cnf gmsm2.param $chaindir - - if $(cp gmcert.cnf gmsm2.param $chaindir) - then - echo "build chain ca succussful!" - else - echo "please input at least Common Name!" - fi -} - -gen_agency_cert_gm() { - local chain="${1}" - local agencypath="${2}" - name=$(getname "$agencypath") - - dir_must_exists "$chain" - file_must_exists "$chain/gmca.key" - check_name agency "$name" - agencydir=$agencypath - dir_must_not_exists "$agencydir" - mkdir -p $agencydir - - $TASSL_CMD genpkey -paramfile $chain/gmsm2.param -out $agencydir/gmagency.key - $TASSL_CMD req -new -subj "/CN=$name/O=fiscobcos/OU=agency" -key $agencydir/gmagency.key -config $chain/gmcert.cnf -out $agencydir/gmagency.csr - $TASSL_CMD x509 -req -CA $chain/gmca.crt -CAkey $chain/gmca.key -days 3650 -CAcreateserial -in $agencydir/gmagency.csr -out $agencydir/gmagency.crt -extfile $chain/gmcert.cnf -extensions v3_agency_root - - cp $chain/gmca.crt $chain/gmcert.cnf $chain/gmsm2.param $agencydir/ - rm -f $agencydir/gmagency.csr - - echo "build $name agency cert successful!" -} - -gen_node_cert_with_extensions_gm() { - capath="$1" - certpath="$2" - name="$3" - type="$4" - extensions="$5" - - $TASSL_CMD genpkey -paramfile $capath/gmsm2.param -out $certpath/gm${type}.key - $TASSL_CMD req -new -subj "/CN=$name/O=fiscobcos/OU=agency" -key $certpath/gm${type}.key -config $capath/gmcert.cnf -out $certpath/gm${type}.csr - $TASSL_CMD x509 -req -CA $capath/gmagency.crt -CAkey $capath/gmagency.key -days 3650 -CAcreateserial -in $certpath/gm${type}.csr -out $certpath/gm${type}.crt -extfile $capath/gmcert.cnf -extensions $extensions - - rm -f $certpath/gm${type}.csr -} - -gen_node_cert_gm() { - if [ "" = "$(openssl ecparam -list_curves 2>&1 | grep secp256k1)" ]; then - exit_with_clean "openssl don't support secp256k1, please upgrade openssl!" - fi - - agpath="${1}" - agency=$(getname "$agpath") - ndpath="${2}" - node=$(getname "$ndpath") - dir_must_exists "$agpath" - file_must_exists "$agpath/gmagency.key" - check_name agency "$agency" - - mkdir -p $ndpath - dir_must_exists "$ndpath" - check_name node "$node" - - mkdir -p $ndpath - gen_node_cert_with_extensions_gm "$agpath" "$ndpath" "$node" node v3_req - gen_node_cert_with_extensions_gm "$agpath" "$ndpath" "$node" ennode v3enc_req - #nodeid is pubkey - $TASSL_CMD ec -in $ndpath/gmnode.key -text -noout | sed -n '7,11p' | sed 's/://g' | tr "\n" " " | sed 's/ //g' | awk '{print substr($0,3);}' | cat > $ndpath/gmnode.nodeid - - #serial - if [ "" != "$($TASSL_CMD version | grep 1.0.2)" ]; - then - $TASSL_CMD x509 -text -in $ndpath/gmnode.crt | sed -n '5p' | sed 's/://g' | tr "\n" " " | sed 's/ //g' | sed 's/[a-z]/\u&/g' | cat > $ndpath/gmnode.serial - else - $TASSL_CMD x509 -text -in $ndpath/gmnode.crt | sed -n '4p' | sed 's/ //g' | sed 's/.*(0x//g' | sed 's/)//g' |sed 's/[a-z]/\u&/g' | cat > $ndpath/gmnode.serial - fi - - - cp $agpath/gmca.crt $agpath/gmagency.crt $ndpath - - cd $ndpath - - echo "build $node node cert successful!" -} - -generate_config_ini() -{ - local output=${1} - local ip=${2} - local offset=${ip_node_counts[${ip//./}]} - local node_groups=(${3//,/ }) - local prefix="" - if [ -n "$guomi_mode" ]; then - prefix="gm" - fi - cat << EOF > ${output} -[rpc] - listen_ip=${listen_ip} - channel_listen_port=$(( offset + port_start[1] )) - jsonrpc_listen_port=$(( offset + port_start[2] )) -[p2p] - listen_ip=0.0.0.0 - listen_port=$(( offset + port_start[0] )) - ;enable_compress=true - ; nodes to connect - $ip_list - -[certificate_blacklist] - ; crl.0 should be nodeid, nodeid's length is 128 - ;crl.0= - -[group] - group_data_path=data/ - group_config_path=${conf_path}/ - -[network_security] - ; directory the certificates located in - data_path=${conf_path}/ - ; the node private key file - key=${prefix}node.key - ; the node certificate file - cert=${prefix}node.crt - ; the ca certificate file - ca_cert=${prefix}ca.crt - -[storage_security] - enable=false - ; the IP of key mananger - key_manager_ip= - ; the Port of key manager - key_manager_port= - cipher_data_key= - -[chain] - id=${chain_id} -[compatibility] - ; supported_version should nerver be changed - supported_version=${compatibility_version} -[log] - enable=true - log_path=./log - ; info debug trace - level=${log_level} - ; MB - max_log_file_size=200 - flush=${auto_flush} - log_flush_threshold=100 -EOF -} - -generate_group_genesis() -{ - local output=$1 - local index=$2 - local node_list=$3 - cat << EOF > ${output} -[consensus] - ; consensus algorithm type, now support PBFT(consensus_type=pbft) and Raft(consensus_type=raft) - consensus_type=${consensus_type} - ; the max number of transactions of a block - max_trans_num=1000 - ; the node id of consensusers - ${node_list} -[state] - ; support mpt/storage - type=${state_type} -[tx] - ; transaction gas limit - gas_limit=300000000 -[group] - id=${index} - timestamp=${timestamp} -EOF -} - -function generate_group_ini() -{ - local output="${1}" - cat << EOF > ${output} -[consensus] - ; the ttl for broadcasting pbft message - ;ttl=2 - ; min block generation time(ms), the max block generation time is 1000 ms - ;min_block_generation_time=500 - ;enable_dynamic_block_size=true -[storage] - ; storage db type, rocksdb / mysql / external, rocksdb is recommended - type=${storage_type} - ; max cache memeory, MB - max_capacity=256 - max_forward_block=10 - ; only for external - max_retry=100 - topic=DB - ; only for mysql - db_ip=127.0.0.1 - db_port=3306 - db_username= - db_passwd= - db_name= -[tx_pool] - limit=150000 -[tx_execute] - enable_parallel=${enable_parallel} -EOF -} - -generate_cert_conf() -{ - local output=$1 - cat << EOF > ${output} -[ca] -default_ca=default_ca -[default_ca] -default_days = 365 -default_md = sha256 - -[req] -distinguished_name = req_distinguished_name -req_extensions = v3_req -[req_distinguished_name] -countryName = CN -countryName_default = CN -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default =GuangDong -localityName = Locality Name (eg, city) -localityName_default = ShenZhen -organizationalUnitName = Organizational Unit Name (eg, section) -organizationalUnitName_default = fisco-bcos -commonName = Organizational commonName (eg, fisco-bcos) -commonName_default = fisco-bcos -commonName_max = 64 - -[ v3_req ] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment - -[ v4_req ] -basicConstraints = CA:TRUE - -EOF -} - -generate_script_template() -{ - local filepath=$1 - cat << EOF > "${filepath}" -#!/bin/bash -SHELL_FOLDER=\$(cd \$(dirname \$0);pwd) - -EOF - chmod +x ${filepath} -} - -generate_cert_conf_gm() -{ - local output=$1 - cat << EOF > ${output} -HOME = . -RANDFILE = $ENV::HOME/.rnd -oid_section = new_oids - -[ new_oids ] -tsa_policy1 = 1.2.3.4.1 -tsa_policy2 = 1.2.3.4.5.6 -tsa_policy3 = 1.2.3.4.5.7 - -#################################################################### -[ ca ] -default_ca = CA_default # The default ca section - -#################################################################### -[ CA_default ] - -dir = ./demoCA # Where everything is kept -certs = $dir/certs # Where the issued certs are kept -crl_dir = $dir/crl # Where the issued crl are kept -database = $dir/index.txt # database index file. -#unique_subject = no # Set to 'no' to allow creation of - # several ctificates with same subject. -new_certs_dir = $dir/newcerts # default place for new certs. - -certificate = $dir/cacert.pem # The CA certificate -serial = $dir/serial # The current serial number -crlnumber = $dir/crlnumber # the current crl number - # must be commented out to leave a V1 CRL -crl = $dir/crl.pem # The current CRL -private_key = $dir/private/cakey.pem # The private key -RANDFILE = $dir/private/.rand # private random number file - -x509_extensions = usr_cert # The extentions to add to the cert - -name_opt = ca_default # Subject Name options -cert_opt = ca_default # Certificate field options - -default_days = 365 # how long to certify for -default_crl_days= 30 # how long before next CRL -default_md = default # use public key default MD -preserve = no # keep passed DN ordering - -policy = policy_match - -[ policy_match ] -countryName = match -stateOrProvinceName = match -organizationName = match -organizationalUnitName = optional -commonName = supplied -emailAddress = optional - -[ policy_anything ] -countryName = optional -stateOrProvinceName = optional -localityName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -emailAddress = optional - -#################################################################### -[ req ] -default_bits = 2048 -default_md = sm3 -default_keyfile = privkey.pem -distinguished_name = req_distinguished_name -x509_extensions = v3_ca # The extentions to add to the self signed cert - -string_mask = utf8only - -# req_extensions = v3_req # The extensions to add to a certificate request - -[ req_distinguished_name ] -countryName = CN -countryName_default = CN -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default =GuangDong -localityName = Locality Name (eg, city) -localityName_default = ShenZhen -organizationalUnitName = Organizational Unit Name (eg, section) -organizationalUnitName_default = fisco -commonName = Organizational commonName (eg, fisco) -commonName_default = fisco -commonName_max = 64 - -[ usr_cert ] -basicConstraints=CA:FALSE -nsComment = "OpenSSL Generated Certificate" - -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid,issuer - -[ v3_req ] - -# Extensions to add to a certificate request - -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature - -[ v3enc_req ] - -# Extensions to add to a certificate request -basicConstraints = CA:FALSE -keyUsage = keyAgreement, keyEncipherment, dataEncipherment - -[ v3_agency_root ] -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid:always,issuer -basicConstraints = CA:true -keyUsage = cRLSign, keyCertSign - -[ v3_ca ] -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid:always,issuer -basicConstraints = CA:true -keyUsage = cRLSign, keyCertSign - -EOF -} - -generate_node_scripts() -{ - local output=$1 - local docker_tag="v${compatibility_version}" - generate_script_template "$output/start.sh" - local ps_cmd="\$(ps aux|grep \${fisco_bcos}|grep -v grep|awk '{print \$2}')" - local start_cmd="nohup \${fisco_bcos} -c config.ini >>nohup.out 2>&1" - local stop_cmd="kill \${node_pid}" - local pid="pid" - local log_cmd="tail -n20 nohup.out" - local check_success="\$(${log_cmd} | grep running)" - if [ ! -z ${docker_mode} ];then - ps_cmd="\$(docker ps |grep \${SHELL_FOLDER//\//} | grep -v grep|awk '{print \$1}')" - start_cmd="docker run -d --rm --name \${SHELL_FOLDER//\//} -v \${SHELL_FOLDER}:/data --network=host -w=/data fiscoorg/fiscobcos:${docker_tag} -c config.ini" - stop_cmd="docker kill \${node_pid} 2>/dev/null" - pid="container id" - log_cmd="tail -n20 \$(docker inspect --format='{{.LogPath}}' \${SHELL_FOLDER//\//})" - check_success="success" - fi - cat << EOF >> "$output/start.sh" -fisco_bcos=\${SHELL_FOLDER}/../${bcos_bin_name} -cd \${SHELL_FOLDER} -node=\$(basename \${SHELL_FOLDER}) -node_pid=${ps_cmd} -if [ ! -z \${node_pid} ];then - echo " \${node} is running, ${pid} is \$node_pid." - exit 0 -else - ${start_cmd} & - sleep 1.5 -fi -try_times=4 -i=0 -while [ \$i -lt \${try_times} ] -do - node_pid=${ps_cmd} - success_flag=${check_success} - if [[ ! -z \${node_pid} && ! -z "\${success_flag}" ]];then - echo -e "\033[32m \${node} start successfully\033[0m" - exit 0 - fi - sleep 0.5 - ((i=i+1)) -done -echo -e "\033[31m Exceed waiting time. Please try again to start \${node} \033[0m" -${log_cmd} -exit 1 -EOF - generate_script_template "$output/stop.sh" - cat << EOF >> "$output/stop.sh" -fisco_bcos=\${SHELL_FOLDER}/../${bcos_bin_name} -node=\$(basename \${SHELL_FOLDER}) -node_pid=${ps_cmd} -try_times=10 -i=0 -if [ -z \${node_pid} ];then - echo " \${node} isn't running." - exit 0 -fi -[ ! -z \${node_pid} ] && ${stop_cmd} > /dev/null -while [ \$i -lt \${try_times} ] -do - sleep 0.6 - node_pid=${ps_cmd} - if [ -z \${node_pid} ];then - echo -e "\033[32m stop \${node} success.\033[0m" - exit 0 - fi - ((i=i+1)) -done -echo " Exceed maximum number of retries. Please try again to stop \${node}" -exit 1 -EOF -} - - -genTransTest() -{ - local output=$1 - local file="${output}/.transTest.sh" - generate_script_template "${file}" - cat << EOF > "${file}" -# This script only support for block number smaller than 65535 - 256 - -ip_port=http://127.0.0.1:$(( port_start[2] )) -trans_num=1 -target_group=1 -version= -if [ \$# -ge 1 ];then - trans_num=\$1 -fi -if [ \$# -ge 2 ];then - target_group=\$2 -fi - -getNodeVersion() -{ - result="\$(curl -X POST --data '{"jsonrpc":"2.0","method":"getClientVersion","params":[],"id":1}' \${ip_port})" - version="\$(echo \${result} | cut -c250- | cut -d \" -f3)" -} - -block_limit() -{ - result=\$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"getBlockNumber","params":['\${target_group}'],"id":83}' \${ip_port}) - if [ \$(echo \${result} | grep -i failed | wc -l) -gt 0 ] || [ -z \${result} ];then - echo "getBlockNumber error!" - exit 1 - fi - blockNumber=\$(echo \${result}| cut -d \" -f 10) - printf "%04x" \$((\$blockNumber+0x100)) -} - -send_a_tx() -{ - limit=\$(block_limit) - random_id="\$(date +%s)\$(printf "%09d" \${RANDOM})" - if [ \${#limit} -gt 4 ];then echo "blockLimit exceed 0xffff, this scripts is unavailable!"; exit 0;fi - if [ "\${version}" == "2.0.0-rc1" ];then - txBytes="f8f0a02ade583745343a8f9a70b40db996fbe69c63531832858\${random_id}85174876e7ff8609184e729fff82\${limit}94d6f1a71052366dbae2f7ab2d5d5845e77965cf0d80b86448f85bce000000000000000000000000000000000000000000000000000000000000001bf5bd8a9e7ba8b936ea704292ff4aaa5797bf671fdc8526dcd159f23c1f5a05f44e9fa862834dc7cb4541558f2b4961dc39eaaf0af7f7395028658d0e01b86a371ca0e33891be86f781ebacdafd543b9f4f98243f7b52d52bac9efa24b89e257a354da07ff477eb0ba5c519293112f1704de86bd2938369fbf0db2dff3b4d9723b9a87d" - else - txBytes="f8eca003eb675ec791c2d19858c91d0046821c27d815e2e9c15\${random_id}0a8402faf08082\${limit}948c17cf316c1063ab6c89df875e96c9f0f5b2f74480b8644ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a464953434f2042434f53000000000000000000000000000000000000000000000101801ba09edf7c0cb63645442aff11323916d51ec5440de979950747c0189f338afdcefda02f3473184513c6a3516e066ea98b7cfb55a79481c9db98e658dd016c37f03dcf" - fi - #echo \$txBytes - curl -s -X POST --data '{"jsonrpc":"2.0","method":"sendRawTransaction","params":['\${target_group}', "'\$txBytes'"],"id":83}' \${ip_port} -} - -send_many_tx() -{ - for j in \$(seq 1 \$1) - do - echo 'Send transaction: ' \$j - send_a_tx \${ip_port} - done -} -getNodeVersion -echo "Use version:\${version}" -send_many_tx \${trans_num} - -EOF -} - -generate_server_scripts() -{ - local output=$1 - genTransTest "${output}" - generate_script_template "$output/start_all.sh" - # echo "ip_array=(\$(ifconfig | grep inet | grep -v inet6 | awk '{print \$2}'))" >> "$output/start_all.sh" - # echo "if echo \${ip_array[@]} | grep -w \"${ip}\" &>/dev/null; then echo \"start node_${ip}_${i}\" && bash \${SHELL_FOLDER}/node_${ip}_${i}/start.sh; fi" >> "${output_dir}/start_all.sh" - cat << EOF >> "$output/start_all.sh" -dirs=(\$(ls -l \${SHELL_FOLDER} | awk '/^d/ {print \$NF}')) -for directory in \${dirs[*]} -do - if [[ -f "\${SHELL_FOLDER}/\${directory}/config.ini" && -f "\${SHELL_FOLDER}/\${directory}/start.sh" ]];then - echo "try to start \${directory}" - bash \${SHELL_FOLDER}/\${directory}/start.sh & - fi -done -wait -EOF - generate_script_template "$output/stop_all.sh" - cat << EOF >> "$output/stop_all.sh" -dirs=(\$(ls -l \${SHELL_FOLDER} | awk '/^d/ {print \$NF}')) -for directory in \${dirs[*]} -do - if [[ -d "\${SHELL_FOLDER}/\${directory}" && -f "\${SHELL_FOLDER}/\${directory}/stop.sh" ]];then - echo "try to stop \${directory}" - bash \${SHELL_FOLDER}/\${directory}/stop.sh & - fi -done -wait -EOF -} - -parse_ip_config() -{ - local config=$1 - n=0 - while read line;do - ip_array[n]=$(echo ${line} | awk '{print $1}') - agency_array[n]=$(echo ${line} | awk '{print $2}') - group_array[n]=$(echo ${line} | awk '{print $3}') - if [ -z "${ip_array[$n]}" -o -z "${agency_array[$n]}" -o -z "${group_array[$n]}" ];then - exit_with_clean "Please check ${config}, make sure there is no empty line!" - fi - ((++n)) - done < ${config} -} - -download_bin() -{ - bin_path=${output_dir}/${bcos_bin_name} - package_name="fisco-bcos.tar.gz" - [ ! -z "${macOS}" ] && package_name="fisco-bcos-macOS.tar.gz" - [ ! -z "$guomi_mode" ] && package_name="fisco-bcos-gm.tar.gz" - if [[ ! -z "$guomi_mode" && ! -z ${macOS} ]];then - exit_with_clean "We don't provide binary of GuoMi on macOS. Please compile source code and use -e option to specific fisco-bcos binary path" - fi - Download_Link="https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v${compatibility_version}/${package_name}" - LOG_INFO "Downloading fisco-bcos binary from ${Download_Link} ..." - if [ $(curl -IL -o /dev/null -s -w %{http_code} https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}) == 200 ];then - curl -LO ${Download_Link} --speed-time 3 --speed-limit 204800 -m ${download_timeout} || { - LOG_INFO "Download speed is too low, try https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name}" - curl -LO https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/v${compatibility_version}/${package_name} - } - else - curl -LO ${Download_Link} - fi - tar -zxf ${package_name} && mv fisco-bcos ${bin_path} && rm ${package_name} - chmod a+x ${bin_path} -} - -check_bin() -{ - echo "Checking fisco-bcos binary..." - bin_version=$(${bin_path} -v) - if [ -z "$(echo ${bin_version} | grep 'FISCO-BCOS')" ];then - exit_with_clean "${bin_path} is wrong. Please correct it and try again." - fi - if [[ ! -z ${guomi_mode} && -z $(echo ${bin_version} | grep 'gm') ]];then - exit_with_clean "${bin_path} isn't gm version. Please correct it and try again." - fi - if [[ -z ${guomi_mode} && ! -z $(echo ${bin_version} | grep 'gm') ]];then - exit_with_clean "${bin_path} isn't standard version. Please correct it and try again." - fi - echo "Binary check passed." -} - -main() -{ -output_dir="$(pwd)/${output_dir}" -[ -z $use_ip_param ] && help 'ERROR: Please set -l or -f option.' -if [ "${use_ip_param}" == "true" ];then - ip_array=(${ip_param//,/ }) -elif [ "${use_ip_param}" == "false" ];then - if ! parse_ip_config $ip_file ;then - exit_with_clean "Parse $ip_file error!" - fi -else - help -fi - - -dir_must_not_exists ${output_dir} -mkdir -p "${output_dir}" - -if [ -z "${compatibility_version}" ];then - compatibility_version=$(curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "tag_name" | grep "\"v2\.[0-9]\.[0-9]\"" | sort -u | tail -n 1 | cut -d \" -f 4 | sed "s/^[vV]//") -fi -# in case network is broken -if [ -z "${compatibility_version}" ];then - compatibility_version="${default_version}" -fi - -# download fisco-bcos and check it -if [ -z ${docker_mode} ];then - if [[ -z ${bin_path} ]];then - download_bin - else - check_bin - fi -fi -if [ -z ${CertConfig} ] || [ ! -e ${CertConfig} ];then - # CertConfig="${output_dir}/cert.cnf" - generate_cert_conf "cert.cnf" -else - cp ${CertConfig} . -fi - -if [ "${use_ip_param}" == "true" ];then - for i in $(seq 0 ${#ip_array[*]});do - agency_array[i]="agency" - group_array[i]=1 - done -fi - -# prepare CA -echo "==============================================================" -if [ ! -e "$ca_file" ]; then - echo "Generating CA key..." - dir_must_not_exists ${output_dir}/chain - gen_chain_cert ${output_dir}/chain >${logfile} 2>&1 || exit_with_clean "openssl error!" - mv ${output_dir}/chain ${output_dir}/cert - if [ "${use_ip_param}" == "false" ];then - for agency_name in ${agency_array[*]};do - if [ ! -d ${output_dir}/cert/${agency_name} ];then - gen_agency_cert ${output_dir}/cert ${output_dir}/cert/${agency_name} >${logfile} 2>&1 - fi - done - else - gen_agency_cert ${output_dir}/cert ${output_dir}/cert/agency >${logfile} 2>&1 - fi - ca_file="${output_dir}/cert/ca.key" -fi - -if [ -n "$guomi_mode" ]; then - check_and_install_tassl - - generate_cert_conf_gm "gmcert.cnf" - - echo "Generating Guomi CA key..." - dir_must_not_exists ${output_dir}/gmchain - gen_chain_cert_gm ${output_dir}/gmchain >${output_dir}/build.log 2>&1 || exit_with_clean "openssl error!" #生成secp256k1算法的CA密钥 - mv ${output_dir}/gmchain ${output_dir}/gmcert - gen_agency_cert_gm ${output_dir}/gmcert ${output_dir}/gmcert/agency >${output_dir}/build.log 2>&1 - ca_file="${output_dir}/gmcert/ca.key" -fi - - -echo "==============================================================" -echo "Generating keys ..." -nodeid_list="" -ip_list="" -count=0 -server_count=0 -groups= -ip_node_counts= -groups_count= -for line in ${ip_array[*]};do - ip=${line%:*} - num=${line#*:} - if [ -z $(echo $ip | grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") ];then - exit_with_clean "Please check IP address: ${ip}" - fi - [ "$num" == "$ip" ] || [ -z "${num}" ] && num=${node_num} - echo "Processing IP:${ip} Total:${num} Agency:${agency_array[${server_count}]} Groups:${group_array[server_count]}" - [ -z "${ip_node_counts[${ip//./}]}" ] && ip_node_counts[${ip//./}]=0 - for ((i=0;i> ${logfile} - node_dir="${output_dir}/${ip}/node${ip_node_counts[${ip//./}]}" - [ -d "${node_dir}" ] && exit_with_clean "${node_dir} exist! Please delete!" - - while : - do - gen_node_cert ${output_dir}/cert/${agency_array[${server_count}]} ${node_dir} >${logfile} 2>&1 - mkdir -p ${conf_path}/ - rm node.param node.private node.pubkey agency.crt - mv *.* ${conf_path}/ - - #private key should not start with 00 - cd ${output_dir} - privateKey=$(openssl ec -in "${node_dir}/${conf_path}/node.key" -text 2> /dev/null| sed -n '3,5p' | sed 's/://g'| tr "\n" " "|sed 's/ //g') - len=${#privateKey} - head2=${privateKey:0:2} - if [ "64" != "${len}" ] || [ "00" == "$head2" ];then - rm -rf ${node_dir} - continue; - fi - - if [ -n "$guomi_mode" ]; then - gen_node_cert_gm ${output_dir}/gmcert/agency ${node_dir} >${output_dir}/build.log 2>&1 - mkdir -p ${gm_conf_path}/ - mv ./*.* ${gm_conf_path}/ - - #private key should not start with 00 - cd ${output_dir} - privateKey=$($TASSL_CMD ec -in "${node_dir}/${gm_conf_path}/gmnode.key" -text 2> /dev/null| sed -n '3,5p' | sed 's/://g'| tr "\n" " "|sed 's/ //g') - len=${#privateKey} - head2=${privateKey:0:2} - if [ "64" != "${len}" ] || [ "00" == "$head2" ];then - rm -rf ${node_dir} - continue; - fi - fi - break; - done - cat ${output_dir}/cert/${agency_array[${server_count}]}/agency.crt >> ${node_dir}/${conf_path}/node.crt - - if [ -n "$guomi_mode" ]; then - cat ${output_dir}/gmcert/agency/gmagency.crt >> ${node_dir}/${gm_conf_path}/gmnode.crt - cat ${output_dir}/gmcert/gmca.crt >> ${node_dir}/${gm_conf_path}/gmnode.crt - - #move origin conf to gm conf - rm ${node_dir}/${conf_path}/node.nodeid - cp ${node_dir}/${conf_path} ${node_dir}/${gm_conf_path}/origin_cert -r - fi - - if [ -n "$guomi_mode" ]; then - nodeid=$($TASSL_CMD ec -in "${node_dir}/${gm_conf_path}/gmnode.key" -text 2> /dev/null | perl -ne '$. > 6 and $. < 12 and ~s/[\n:\s]//g and print' | perl -ne 'print substr($_, 2)."\n"') - else - nodeid=$(openssl ec -in "${node_dir}/${conf_path}/node.key" -text 2> /dev/null | perl -ne '$. > 6 and $. < 12 and ~s/[\n:\s]//g and print' | perl -ne 'print substr($_, 2)."\n"') - fi - - if [ -n "$guomi_mode" ]; then - #remove original cert files - rm ${node_dir:?}/${conf_path} -rf - mv ${node_dir}/${gm_conf_path} ${node_dir}/${conf_path} - fi - - - if [ "${use_ip_param}" == "false" ];then - node_groups=(${group_array[server_count]//,/ }) - for j in ${node_groups[@]};do - if [ -z "${groups_count[${j}]}" ];then groups_count[${j}]=0;fi - echo "groups_count[${j}]=${groups_count[${j}]}" >> ${logfile} - groups[${j}]=$"${groups[${j}]}node.${groups_count[${j}]}=${nodeid} - " - ((++groups_count[j])) - done - else - nodeid_list=$"${nodeid_list}node.${count}=${nodeid} - " - fi - - ip_list=$"${ip_list}node.${count}="${ip}:$(( ${ip_node_counts[${ip//./}]} + port_start[0] ))" - " - ip_node_counts[${ip//./}]=$(( ${ip_node_counts[${ip//./}]} + 1 )) - ((++count)) - done - sdk_path="${output_dir}/${ip}/sdk" - if [ ! -d ${sdk_path} ];then - gen_node_cert ${output_dir}/cert/${agency_array[${server_count}]} "${sdk_path}">${logfile} 2>&1 - cat ${output_dir}/cert/${agency_array[${server_count}]}/agency.crt >> node.crt - rm node.param node.private node.pubkey node.nodeid agency.crt - cp ${output_dir}/cert/ca.crt ${sdk_path}/ - cd ${output_dir} - fi - ((++server_count)) -done - -ip_node_counts=() -echo "==============================================================" -echo "Generating configurations..." -cd ${current_dir} -server_count=0 -for line in ${ip_array[*]};do - ip=${line%:*} - num=${line#*:} - [ "$num" == "$ip" ] || [ -z "${num}" ] && num=${node_num} - [ -z "${ip_node_counts[${ip//./}]}" ] && ip_node_counts[${ip//./}]=0 - echo "Processing IP:${ip} Total:${num} Agency:${agency_array[${server_count}]} Groups:${group_array[server_count]}" - for ((i=0;i> ${logfile} - node_dir="${output_dir}/${ip}/node${ip_node_counts[${ip//./}]}" - generate_config_ini "${node_dir}/config.ini" ${ip} "${group_array[server_count]}" - if [ "${use_ip_param}" == "false" ];then - node_groups=(${group_array[${server_count}]//,/ }) - for j in ${node_groups[@]};do - generate_group_genesis "$node_dir/${conf_path}/group.${j}.genesis" "${j}" "${groups[${j}]}" - generate_group_ini "$node_dir/${conf_path}/group.${j}.ini" - done - else - generate_group_genesis "$node_dir/${conf_path}/group.1.genesis" "1" "${nodeid_list}" - generate_group_ini "$node_dir/${conf_path}/group.1.ini" - fi - generate_node_scripts "${node_dir}" - ip_node_counts[${ip//./}]=$(( ${ip_node_counts[${ip//./}]} + 1 )) - done - generate_server_scripts "${output_dir}/${ip}" - if [ -z ${docker_mode} ];then cp "$bin_path" "${output_dir}/${ip}/fisco-bcos"; fi - if [ -n "$make_tar" ];then cd ${output_dir} && tar zcf "${ip}.tar.gz" "${ip}" && cd ${current_dir};fi - ((++server_count)) -done -rm ${logfile} -if [ "${use_ip_param}" == "false" ];then -echo "==============================================================" - for l in $(seq 0 ${#groups_count[@]});do - if [ ! -z "${groups_count[${l}]}" ];then echo "Group:${l} has ${groups_count[${l}]} nodes";fi - done -fi - -} - -check_env -parse_params $@ -main -print_result diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 6e37ea1..49fe950 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -43,6 +43,7 @@ def installNode(): node_rpcPort = int(getCommProperties("node.rpcPort")) fisco_version = getCommProperties("fisco.version") node_counts = getCommProperties("node.counts") + encrypt_type = getCommProperties("encrypt.type") if if_exist_fisco == "no": print ("================================================================") @@ -58,10 +59,14 @@ def installNode(): node_nums = int(node_counts) doCmd('sed -i "s/nodeCounts/{}/g" nodeconf'.format(node_nums)) + gitComm = "wget https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/{}/build_chain.sh && chmod u+x build_chain.sh".format(fisco_version) if not os.path.exists("{}/nodes".format(currentDir)): - doCmdIgnoreException("chmod u+x *.sh") - doCmdIgnoreException("dos2unix *.sh") - os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + print (gitComm) + os.system(gitComm) + if encrypt_type == 1: + os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i -g".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + else: + os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) else: info = "n" if sys.version_info.major == 2: @@ -71,9 +76,12 @@ def installNode(): if info == "y" or info == "Y": doCmdIgnoreException("bash nodes/127.0.0.1/stop_all.sh") doCmd("rm -rf nodes") - doCmdIgnoreException("chmod u+x *.sh") - doCmdIgnoreException("dos2unix *.sh") - os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + print (gitComm) + os.system(gitComm) + if encrypt_type == 1: + os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i -g".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) + else: + os.system("bash build_chain.sh -f nodeconf -p {},{},{} -v {} -i".format(node_p2pPort, node_channelPort, node_rpcPort, fisco_version)) startNode() def startNode(): @@ -196,24 +204,37 @@ def changeManagerConfig(): mysql_user = getCommProperties("mysql.user") mysql_password = getCommProperties("mysql.password") mysql_database = getCommProperties("mysql.database") + encrypt_type = getCommProperties("encrypt.type") # init file server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" + script_dir_gm = script_dir + "/gm" conf_dir = server_dir + "/conf" if not os.path.exists(script_dir + "/temp.sh"): - doCmd('cp -f {}/webase.sh {}/temp.sh'.format(script_dir, script_dir)) + if encrypt_type == 1: + doCmd('cp -f {}/webase-gm.sh {}/temp-gm.sh'.format(script_dir_gm, script_dir_gm)) + else: + doCmd('cp -f {}/webase.sh {}/temp.sh'.format(script_dir, script_dir)) else: - doCmd('cp -f {}/temp.sh {}/webase.sh'.format(script_dir, script_dir)) + if encrypt_type == 1: + doCmd('cp -f {}/temp-gm.sh {}/webase-gm.sh'.format(script_dir_gm, script_dir_gm)) + else: + doCmd('cp -f {}/temp.sh {}/webase.sh'.format(script_dir, script_dir)) if not os.path.exists(conf_dir + "/temp.yml"): doCmd('cp -f {}/application.yml {}/temp.yml'.format(conf_dir, conf_dir)) else: doCmd('cp -f {}/temp.yml {}/application.yml'.format(conf_dir, conf_dir)) # change script config - doCmd('sed -i "s/defaultAccount/{}/g" {}/webase.sh'.format(mysql_user, script_dir)) - doCmd('sed -i "s/defaultPassword/{}/g" {}/webase.sh'.format(mysql_password, script_dir)) - doCmd('sed -i "s/webasenodemanager/{}/g" {}/webase.sh'.format(mysql_database, script_dir)) + if encrypt_type == 1: + doCmd('sed -i "s/defaultAccount/{}/g" {}/webase-gm.sh'.format(mysql_user, script_dir_gm)) + doCmd('sed -i "s/defaultPassword/{}/g" {}/webase-gm.sh'.format(mysql_password, script_dir_gm)) + doCmd('sed -i "s/webasenodemanager/{}/g" {}/webase-gm.sh'.format(mysql_database, script_dir_gm)) + else: + doCmd('sed -i "s/defaultAccount/{}/g" {}/webase.sh'.format(mysql_user, script_dir)) + doCmd('sed -i "s/defaultPassword/{}/g" {}/webase.sh'.format(mysql_password, script_dir)) + doCmd('sed -i "s/webasenodemanager/{}/g" {}/webase.sh'.format(mysql_database, script_dir)) # change server config doCmd('sed -i "s/5001/{}/g" {}/application.yml'.format(mgr_port, conf_dir)) @@ -222,6 +243,7 @@ def changeManagerConfig(): doCmd('sed -i "s/defaultAccount/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) doCmd('sed -i "s/defaultPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) doCmd('sed -i "s/webasenodemanager/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) + doCmd('sed -i "s%encryptType: 0%encryptType: {}%g" {}/application.yml'.format(encrypt_type, conf_dir)) return @@ -230,6 +252,7 @@ def installManager(): print ("============== WeBASE-Node-Manager install... ==============") os.chdir(currentDir) version = getCommProperties("webase.version") + encrypt_type = getCommProperties("encrypt.type") gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) pullSourceExtract(gitComm,"webase-node-mgr") changeManagerConfig() @@ -239,6 +262,10 @@ def installManager(): mysql_port = getCommProperties("mysql.port") server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" + script_cmd = 'bash webase.sh {} {}'.format(mysql_ip, mysql_port) + if encrypt_type == 1: + script_dir = script_dir + "/gm" + script_cmd = 'bash webase-gm.sh {} {}'.format(mysql_ip, mysql_port) if len(sys.argv) == 3 and sys.argv[2] == "travis": print ("Travis CI do not initialize database") @@ -252,7 +279,7 @@ def installManager(): os.chdir(script_dir) doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") - dbResult = doCmd('bash webase.sh {} {}'.format(mysql_ip, mysql_port)) + dbResult = doCmd(script_cmd) if dbResult["status"] == 0: if_success = 'success' in dbResult["output"] if if_success: @@ -325,6 +352,7 @@ def changeFrontConfig(): nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") frontDb = getCommProperties("front.h2.name") + encrypt_type = getCommProperties("encrypt.type") if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") @@ -335,6 +363,8 @@ def changeFrontConfig(): # init file server_dir = currentDir + "/webase-front/conf" + if encrypt_type == 1: + server_dir = currentDir + "/webase-front-gm/conf" if not os.path.exists(server_dir + "/temp.yml"): doCmd('cp -f {}/application.yml {}/temp.yml'.format(server_dir, server_dir)) else: @@ -344,6 +374,7 @@ def changeFrontConfig(): doCmd('sed -i "s/5002/{}/g" {}/application.yml'.format(frontPort, server_dir)) doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) + doCmd('sed -i "s%encryptType: 0%encryptType: {}%g" {}/application.yml'.format(encrypt_type, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) @@ -356,8 +387,14 @@ def installFront(): print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) version = getCommProperties("webase.version") + encrypt_type = getCommProperties("encrypt.type") gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) - pullSourceExtract(gitComm,"webase-front") + frontPackage = "webase-front" + if encrypt_type == 1: + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front-gm.zip".format(version) + frontPackage = "webase-front-gm" + server_dir = currentDir + "/" + frontPackage + pullSourceExtract(gitComm,frontPackage) changeFrontConfig() # check front db @@ -383,7 +420,6 @@ def installFront(): if not os.path.exists(sdk_dir): print ("======= FISCO-BCOS sdk dir:{} is not exist. please check! =======".format(sdk_dir)) sys.exit(0) - server_dir = currentDir + "/webase-front" os.chdir(server_dir) copyFiles(fisco_dir + "/sdk", server_dir + "/conf") @@ -394,8 +430,11 @@ def startFront(): print ("============== WeBASE-Front start... ==============") os.chdir(currentDir) frontPort = getCommProperties("front.port") - server_dir = currentDir + "/webase-front" - os.chdir(server_dir) + encrypt_type = getCommProperties("encrypt.type") + frontPackage = "webase-front" + if encrypt_type == 1: + frontPackage = "webase-front-gm" + os.chdir(currentDir + "/" + frontPackage) doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") doCmdIgnoreException("dos2unix *.sh") @@ -415,7 +454,7 @@ def startFront(): if if_success: print ("======= WeBASE-Front start success! =======") else: - print ("======= WeBASE-Front start fail. Please view log file (default path:./webase-front/log/). =======") + print ("======= WeBASE-Front start fail. Please view log file (default path:./{}/log/). =======".format(frontPackage)) sys.exit(0) else: print ("======= WeBASE-Front start fail. Please view log file (default path:./log/). =======") @@ -425,7 +464,11 @@ def startFront(): return def stopFront(): + os.chdir(currentDir) + encrypt_type = getCommProperties("encrypt.type") server_dir = currentDir + "/webase-front" + if encrypt_type == 1: + server_dir = currentDir + "/webase-front-gm" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") diff --git a/deploy/common.properties b/deploy/common.properties index 86a1a84..a5aab0b 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -29,9 +29,12 @@ node.channelPort=20200 # Node rpc service port node.rpcPort=8545 -# Use existing chain or not(yes/no) +# Use existing chain or not (yes/no) if.exist.fisco=no +# Encrypt type (0: standard, 1: guomi) +encrypt.type=0 + # Configuration is required when using the existing chain # The path of the existing chain, the path of the start_all.sh script # Under the path, there should be a SDK directory where the SDK certificates (ca.crt, node.crt and node. Key) are stored @@ -42,6 +45,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.0.0 +fisco.version=2.1.0 # Number of building nodes (two by default) node.counts=nodeCounts \ No newline at end of file From 6aa13f046254df7db2827a7b6227f0c7b059c79e Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Fri, 13 Dec 2019 18:33:37 +0800 Subject: [PATCH 085/119] encrypt.type to int --- deploy/comm/build.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 49fe950..86664fb 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -43,7 +43,7 @@ def installNode(): node_rpcPort = int(getCommProperties("node.rpcPort")) fisco_version = getCommProperties("fisco.version") node_counts = getCommProperties("node.counts") - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) if if_exist_fisco == "no": print ("================================================================") @@ -204,21 +204,21 @@ def changeManagerConfig(): mysql_user = getCommProperties("mysql.user") mysql_password = getCommProperties("mysql.password") mysql_database = getCommProperties("mysql.database") - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) # init file server_dir = currentDir + "/webase-node-mgr" script_dir = server_dir + "/script" script_dir_gm = script_dir + "/gm" conf_dir = server_dir + "/conf" - if not os.path.exists(script_dir + "/temp.sh"): - if encrypt_type == 1: + if encrypt_type == 1: + if not os.path.exists(script_dir + "/temp-gm.sh"): doCmd('cp -f {}/webase-gm.sh {}/temp-gm.sh'.format(script_dir_gm, script_dir_gm)) else: - doCmd('cp -f {}/webase.sh {}/temp.sh'.format(script_dir, script_dir)) - else: - if encrypt_type == 1: doCmd('cp -f {}/temp-gm.sh {}/webase-gm.sh'.format(script_dir_gm, script_dir_gm)) + else: + if not os.path.exists(script_dir + "/temp.sh"): + doCmd('cp -f {}/webase.sh {}/temp.sh'.format(script_dir, script_dir)) else: doCmd('cp -f {}/temp.sh {}/webase.sh'.format(script_dir, script_dir)) if not os.path.exists(conf_dir + "/temp.yml"): @@ -252,7 +252,7 @@ def installManager(): print ("============== WeBASE-Node-Manager install... ==============") os.chdir(currentDir) version = getCommProperties("webase.version") - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) pullSourceExtract(gitComm,"webase-node-mgr") changeManagerConfig() @@ -352,7 +352,7 @@ def changeFrontConfig(): nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") frontDb = getCommProperties("front.h2.name") - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) if_exist_fisco = getCommProperties("if.exist.fisco") fisco_dir = getCommProperties("fisco.dir") @@ -387,7 +387,7 @@ def installFront(): print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) version = getCommProperties("webase.version") - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) frontPackage = "webase-front" if encrypt_type == 1: @@ -430,7 +430,7 @@ def startFront(): print ("============== WeBASE-Front start... ==============") os.chdir(currentDir) frontPort = getCommProperties("front.port") - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) frontPackage = "webase-front" if encrypt_type == 1: frontPackage = "webase-front-gm" @@ -465,7 +465,7 @@ def startFront(): def stopFront(): os.chdir(currentDir) - encrypt_type = getCommProperties("encrypt.type") + encrypt_type = int(getCommProperties("encrypt.type")) server_dir = currentDir + "/webase-front" if encrypt_type == 1: server_dir = currentDir + "/webase-front-gm" From d1c0f1ae885829a3d96cea0ae87be0b0e661361d Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Wed, 18 Dec 2019 16:41:03 +0800 Subject: [PATCH 086/119] test optimize --- deploy/comm/build.py | 4 ++-- deploy/common.properties | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 86664fb..b43a2f3 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -59,7 +59,7 @@ def installNode(): node_nums = int(node_counts) doCmd('sed -i "s/nodeCounts/{}/g" nodeconf'.format(node_nums)) - gitComm = "wget https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/{}/build_chain.sh && chmod u+x build_chain.sh".format(fisco_version) + gitComm = "wget https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v{}/build_chain.sh && chmod u+x build_chain.sh".format(fisco_version) if not os.path.exists("{}/nodes".format(currentDir)): print (gitComm) os.system(gitComm) @@ -212,7 +212,7 @@ def changeManagerConfig(): script_dir_gm = script_dir + "/gm" conf_dir = server_dir + "/conf" if encrypt_type == 1: - if not os.path.exists(script_dir + "/temp-gm.sh"): + if not os.path.exists(script_dir_gm + "/temp-gm.sh"): doCmd('cp -f {}/webase-gm.sh {}/temp-gm.sh'.format(script_dir_gm, script_dir_gm)) else: doCmd('cp -f {}/temp-gm.sh {}/webase-gm.sh'.format(script_dir_gm, script_dir_gm)) diff --git a/deploy/common.properties b/deploy/common.properties index a5aab0b..4f4dbcf 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -29,12 +29,12 @@ node.channelPort=20200 # Node rpc service port node.rpcPort=8545 -# Use existing chain or not (yes/no) -if.exist.fisco=no - # Encrypt type (0: standard, 1: guomi) encrypt.type=0 +# Use existing chain or not (yes/no) +if.exist.fisco=no + # Configuration is required when using the existing chain # The path of the existing chain, the path of the start_all.sh script # Under the path, there should be a SDK directory where the SDK certificates (ca.crt, node.crt and node. Key) are stored From 9f1a780313ce0be72116668ff9987d490192b18b Mon Sep 17 00:00:00 2001 From: v_sflkchen Date: Tue, 24 Dec 2019 15:10:32 +0800 Subject: [PATCH 087/119] optimize tips --- deploy/comm/build.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index b43a2f3..577c6f1 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -305,21 +305,16 @@ def startManager(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_occupied = 'been occupied' in result["output"] - if if_occupied: - pid = get_str_btw(result["output"], "(", ")") - print ("Port {} has been occupied by other server PID({})".format(managerPort,pid)) - sys.exit(0) if_started = 'is running' in result["output"] if if_started: pid = get_str_btw(result["output"], "(", ")") print ("WeBASE-Node-Manager Port {} is running PID({})".format(managerPort,pid)) sys.exit(0) - if_success = 'Success' in result["output"] + if_success = 'Starting' in result["output"] if if_success: - print ("======= WeBASE-Node-Manager start success! =======") + print ("======= WeBASE-Node-Manager starting . Please check through the log file (default path:./webase-node-mgr/log/). =======") else: - print ("======= WeBASE-Node-Manager start fail. Please view log file (default path:./webase-node-mgr/log/). =======") + print ("======= WeBASE-Node-Manager start fail. Please check through the log file (default path:./webase-node-mgr/log/). =======") sys.exit(0) else: print ("======= WeBASE-Node-Manager start fail. Please view log file (default path:./log/). =======") @@ -440,21 +435,16 @@ def startFront(): doCmdIgnoreException("dos2unix *.sh") result = doCmd("bash start.sh") if result["status"] == 0: - if_occupied = 'been occupied' in result["output"] - if if_occupied: - pid = get_str_btw(result["output"], "(", ")") - print ("Port {} has been occupied by other server PID({})".format(frontPort,pid)) - sys.exit(0) if_started = 'is running' in result["output"] if if_started: pid = get_str_btw(result["output"], "(", ")") print ("WeBASE-Front Port {} is running PID({})".format(frontPort,pid)) sys.exit(0) - if_success = 'Success' in result["output"] + if_success = 'Starting' in result["output"] if if_success: - print ("======= WeBASE-Front start success! =======") + print ("======= WeBASE-Front starting . Please check through the log file (default path:./{}/log/). =======".format(frontPackage)) else: - print ("======= WeBASE-Front start fail. Please view log file (default path:./{}/log/). =======".format(frontPackage)) + print ("======= WeBASE-Front start fail. Please check through the log file (default path:./{}/log/). =======".format(frontPackage)) sys.exit(0) else: print ("======= WeBASE-Front start fail. Please view log file (default path:./log/). =======") From 3396a6632d68b5d0c8aad523622383774690e21d Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 17:19:55 +0800 Subject: [PATCH 088/119] update readme --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a6f6272..9a7d0cc 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,29 @@ # 什么是WeBASE -WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和fisco bcos节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。详细介绍请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) +**WeBASE**(WeBank Blockchain Application Software Extension) 是在区块链应用和FISCO BCOS节点之间搭建的一套通用组件,围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块;开发者可以根据业务所需,选择子系统进行部署。 -WeBASE将区块链应用开发标准化,搭建完fisco bcos节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) +**WeBASE**屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含**节点前置**、**节点管理**、**Web管理平台**、**签名服务**、**数据导出**等子系统。 + +**WeBASE**将区块链应用开发标准化,搭建完FISCO BCOS节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) + +**一键部署(FISCO BCOS + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web)**可以参考文档[WeBASE一键部署](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**整体结构设计与各子系统功能与安装部署的详细介绍,请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) ## 各子系统简介 * **节点前置服务 [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** -集成web3jsdk,提供restful风格的接口,客户端可以使用http的形式和节点进行交互,内置内存数据库,采集节点健康度数据。内置web控制台,实现节点的可视化操作。 +集成web3sdk,提供restful风格的接口,客户端可以使用http的形式和节点进行交互,内置内存数据库,采集节点健康度数据。内置web控制台,实现节点的可视化、合约部署IDE等功能。 * **节点管理服务 [WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** -处理前端页面所有web请求,管理各个节点的状态,管理链上所有智能合约,对区块链的数据进行统计、分析,对异常交易的审计,私钥管理等。 +处理WeBASE-Web前端页面所有web请求,基于前置服务,管理各个节点的状态,管理链上所有智能合约,对区块链的数据进行统计、分析,对异常交易的审计,私钥管理等。 * **WeBASE管理平台 [WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** -可视化操作平台,可基于此平台查看节点信息,开发智能合约等。 +基于节点管理服务的可视化操作平台,可基于此平台查看节点信息,开发智能合约等。 * **交易服务 [WeBASE-Transcation](https://github.com/WeBankFinTech/WeBASE-Transcation)** -接收交易请求,缓存交易到数据库中,异步上链,可大幅提升吞吐量,解决区块链的tps瓶颈。 +接收交易请求,缓存交易到数据库中,异步上链,可大幅提升吞吐量,解决区块链的tps瓶颈问题。 * **私钥托管和签名服务 [WeBASE-Sign](https://github.com/WeBankFinTech/WeBASE-Sign)** 托管用户私钥,提供云端签名。 + * **数据导出代码生成工具 [WeBASE-Codegen-Monkey](https://github.com/WeBankFinTech/WeBASE-Codegen-Monkey)** 代码生成工具,通过配置可以生成数据导出的核心代码。 From 8b8b6f1244777478c152bae5d332bae6b6ff6535 Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 17:43:08 +0800 Subject: [PATCH 089/119] fix title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a7d0cc..1354717 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **WeBASE**将区块链应用开发标准化,搭建完FISCO BCOS节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) -**一键部署(FISCO BCOS + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web)**可以参考文档[WeBASE一键部署](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**整体结构设计与各子系统功能与安装部署的详细介绍,请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) +**WeBASE一键部署**(FISCO BCOS + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web)可以参考[WeBASE一键部署文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**整体结构设计与各子系统功能与安装部署的详细介绍,请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) ## 各子系统简介 * **节点前置服务 [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** From 4b17eb9228f93be425fb9a19f506cdd402b3ca78 Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 17:59:08 +0800 Subject: [PATCH 090/119] fix webase version v1.2.2 --- deploy/common.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/common.properties b/deploy/common.properties index 4f4dbcf..c90e0bf 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,7 @@ [common] # Webase Version (V1.1.0 or above) -webase.version=v1.2.1 +webase.version=v1.2.2 # Mysql database configuration of WeBASE-NodeManager mysql.ip=localhost From a5d013d9b01f30efc797c3f0d70c56fab2c2cdb6 Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 17:59:59 +0800 Subject: [PATCH 091/119] update fisco 2.0+ --- deploy/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/install.md b/deploy/install.md index 0480382..2342639 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -1,7 +1,7 @@ # 快速搭建 ## 简介 -一键部署可以快速搭建WeBASE管理台环境。包括节点(FISCO-BCOS 2.0)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。其中,节点的搭建是可选的,可以通过配置来选择使用已有链或者搭建新链。详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html)。 +一键部署可以快速搭建WeBASE管理台环境。包括节点(FISCO-BCOS 2.0+)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。其中,节点的搭建是可选的,可以通过配置来选择使用已有链或者搭建新链。详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html)。 ## 贡献说明 请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 From 8726ca21efe1c40f49c8f30804f62adba3d8678c Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 18:00:18 +0800 Subject: [PATCH 092/119] update en's readme --- README-en.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README-en.md b/README-en.md index 8faf831..1ce2805 100644 --- a/README-en.md +++ b/README-en.md @@ -4,22 +4,26 @@ # What's WeBASE? -WeBASE (WeBank Blockchain Application Software Extension) is a set of general components building between blockchain application and FISCO-BCOS Nodes. Each module is designed around transaction, contract, key management, data and visual management. Developers can choose subsystems for deployment according to business needs. WeBASE shields the complexity of the bottom layer of the blockchain, reduces the threshold of developers, and greatly improves the development efficiency of the blockchain application. it includes subsystems such as node front, node management, transaction link, data export, web management platform, etc.For details, please refer to [WeBASE Online documentation](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) +**WeBASE** (WeBank Blockchain Application Software Extension) is a set of general components building between blockchain application and FISCO-BCOS Nodes. Each module is designed around blockchain transaction, contract, key management, data and visual management. Developers can choose subsystems for deployment according to business needs. -WeBASE standardizes the application and development of blockchain. After building the FISCO BCOS nodes, only five step standard process is needed for the application and development of blockchain. For the development process, please refer to [using webase to develop blockchain application](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) +**WeBASE** shields the complexity of the bottom layer of the blockchain, reduces the threshold of developers, and greatly improves the development efficiency of the blockchain application. It includes subsystems such as node front, node management, web management platform, sign service, data export etc.. + +**WeBASE** standardizes the application and development of blockchain. After building the FISCO BCOS nodes, only five steps needed to develop and build the application of blockchain. For details of developing process, please refer to [Using WeBASE to develop blockchain application](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) + + **WeBASE One Click Installation** (including FISCO BCOS nodes + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web) refers to [WeBASE One-Click-Installation Documentation](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**'s The overall structure design and the detailed introduction of the functions and installation of each subsystem, please refer to [WeBASE Online Documentation](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) ## Subsystem introduction * **Node Front service [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** -It integrates web3jsdk and provides restful interface. The client can interact with the node in the form of HTTP. The built-in memory database collects the health data of the node. Built in Web console to realize the visual operation of nodes. +It integrates web3jsdk and provides restful interface. The client can interact with the node in the form of HTTP. The built-in memory database collects the health data of the node. Built in Web console to realize the visual operation of nodes and solidity IDE etc.. * **Node management service [WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** -Handle all web requests on the front-end page, manage the status of each node, manage all smart contracts on the chain, make statistics and Analysis on the data of the blockchain, audit abnormal transactions, private key management, etc. +Handle all web requests from WeBASE-Web pages, manage the status of each node, manage all smart contracts on the chain, make statistics and Analysis on the data of the blockchain, audit abnormal transactions, private key management, etc. * **WeBASE management platform [WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** Visual operation platform, based on which node information can be viewed and smart contracts can be developed. * **Transcation service [WeBASE-Transcation](https://github.com/WeBankFinTech/WeBASE-Transcation)** -Receive transaction request, cache transaction to database and asynchronously chain up, which can greatly improve throughput and solve the TPS bottleneck of blockchain. +Receive transaction request, cache transaction to database and asynchronously chain up, which can greatly improve throughput and solve the TPS bottleneck problem of blockchain. * **Private key Hosting and cloud signature service [WeBASE-Sign](https://github.com/WeBankFinTech/WeBASE-Sign)** Hosting user private key, providing cloud signature. From 6a7e57d144649e532d32996e85bb73e5e22dc813 Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 18:04:26 +0800 Subject: [PATCH 093/119] fix sentence --- README-en.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-en.md b/README-en.md index 1ce2805..c9893b2 100644 --- a/README-en.md +++ b/README-en.md @@ -10,11 +10,11 @@ **WeBASE** standardizes the application and development of blockchain. After building the FISCO BCOS nodes, only five steps needed to develop and build the application of blockchain. For details of developing process, please refer to [Using WeBASE to develop blockchain application](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) - **WeBASE One Click Installation** (including FISCO BCOS nodes + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web) refers to [WeBASE One-Click-Installation Documentation](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**'s The overall structure design and the detailed introduction of the functions and installation of each subsystem, please refer to [WeBASE Online Documentation](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) + **WeBASE One Click Installation** (including FISCO BCOS nodes + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web) refers to [WeBASE One-Click-Installation Documentation](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**'s overall structure design and the detailed introduction of the functions and installation of each subsystem, please refer to [WeBASE Online Documentation](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) ## Subsystem introduction * **Node Front service [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** -It integrates web3jsdk and provides restful interface. The client can interact with the node in the form of HTTP. The built-in memory database collects the health data of the node. Built in Web console to realize the visual operation of nodes and solidity IDE etc.. +It integrates web3sdk and provides restful interface. The client can interact with the node in the form of HTTP. The built-in memory database collects the health data of the node. Built in Web console to realize the visual operation of nodes and solidity IDE etc.. * **Node management service [WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** Handle all web requests from WeBASE-Web pages, manage the status of each node, manage all smart contracts on the chain, make statistics and Analysis on the data of the blockchain, audit abnormal transactions, private key management, etc. From ea8ca886d0965efef406ac88b90cac8a317702c8 Mon Sep 17 00:00:00 2001 From: codingcattwo <847701726@qq.com> Date: Tue, 31 Dec 2019 18:05:26 +0800 Subject: [PATCH 094/119] fix sentence add front --- README-en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-en.md b/README-en.md index c9893b2..9d1f39a 100644 --- a/README-en.md +++ b/README-en.md @@ -17,7 +17,7 @@ It integrates web3sdk and provides restful interface. The client can interact with the node in the form of HTTP. The built-in memory database collects the health data of the node. Built in Web console to realize the visual operation of nodes and solidity IDE etc.. * **Node management service [WeBASE-Node-Manager](https://github.com/WeBankFinTech/WeBASE-Node-Manager)** -Handle all web requests from WeBASE-Web pages, manage the status of each node, manage all smart contracts on the chain, make statistics and Analysis on the data of the blockchain, audit abnormal transactions, private key management, etc. +Based on WeBASE-Front, handle all web requests from WeBASE-Web pages, manage the status of each node, manage all smart contracts on the chain, make statistics and Analysis on the data of the blockchain, audit abnormal transactions, private key management, etc. * **WeBASE management platform [WeBASE-Web](https://github.com/WeBankFinTech/WeBASE-Web)** Visual operation platform, based on which node information can be viewed and smart contracts can be developed. From bd091311beef6672ad636c83d9e07382f54164a8 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 12 Mar 2020 12:00:37 +0800 Subject: [PATCH 095/119] fix webase different version --- deploy/comm/build.py | 23 ++++++++++++++--------- deploy/common.properties | 4 +++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 577c6f1..c557205 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -17,8 +17,13 @@ def do(): installFront() print ("===================== deploy end... =====================") os.chdir(currentDir) - version = getCommProperties("webase.version") - print ("===================== version {} =====================".format(version)) + web_version = getCommProperties("webase.web.version") + mgr_version = getCommProperties("webase.mgr.version") + front_version = getCommProperties("webase.front.version") + + print ("===================== webase-web version {} =====================".format(web_version)) + print ("===================== webase-node-mgr version {} =====================".format(mgr_version)) + print ("===================== webase-front version {} =====================".format(front_version)) print ("================================================================") return @@ -145,8 +150,8 @@ def installWeb(): print ("================================================================") print ("============== WeBASE-Web install... ==============") os.chdir(currentDir) - version = getCommProperties("webase.version") - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(version) + web_version = getCommProperties("webase.web.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(web_version) pullSourceExtract(gitComm,"webase-web") changeWebConfig() startWeb() @@ -251,9 +256,9 @@ def installManager(): print ("================================================================") print ("============== WeBASE-Node-Manager install... ==============") os.chdir(currentDir) - version = getCommProperties("webase.version") + mgr_version = getCommProperties("webase.mgr.version") encrypt_type = int(getCommProperties("encrypt.type")) - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(mgr_version) pullSourceExtract(gitComm,"webase-node-mgr") changeManagerConfig() dbConnect() @@ -381,12 +386,12 @@ def installFront(): print ("================================================================") print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) - version = getCommProperties("webase.version") + front_version = getCommProperties("webase.front.version") encrypt_type = int(getCommProperties("encrypt.type")) - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(front_version) frontPackage = "webase-front" if encrypt_type == 1: - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front-gm.zip".format(version) + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front-gm.zip".format(front_version) frontPackage = "webase-front-gm" server_dir = currentDir + "/" + frontPackage pullSourceExtract(gitComm,frontPackage) diff --git a/deploy/common.properties b/deploy/common.properties index c90e0bf..c9056ae 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,7 +1,9 @@ [common] # Webase Version (V1.1.0 or above) -webase.version=v1.2.2 +webase.web.version=v1.2.2 +webase.mgr.version=v1.2.2 +webase.front.version=v1.2.3 # Mysql database configuration of WeBASE-NodeManager mysql.ip=localhost From 83c9335f17c3576ed20902f7ab5f1f79787ca758 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 12 Mar 2020 15:30:14 +0800 Subject: [PATCH 096/119] update md for 1.3.0 --- README.md | 2 +- deploy/README-en.md | 2 +- deploy/install.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0f02113..73a5655 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ **WeBASE**将区块链应用开发标准化,搭建完FISCO BCOS节点后,只需按照五步标准流程进行区块链应用开发,开发流程请参阅 [使用WeBASE开发区块链应用](https://github.com/WeBankFinTech/WeBASE-Doc/blob/master/docs/WeBASE/quick-start.md) -**WeBASE一键部署**(FISCO BCOS + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Web)可以参考[WeBASE一键部署文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**整体结构设计与各子系统功能与安装部署的详细介绍,请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) +**WeBASE一键部署**(FISCO BCOS + WeBASE-Front + WeBASE-Node-Manager + WeBASE-Sign + WeBASE-Web)可以参考[WeBASE一键部署文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html),**WeBASE**整体结构设计与各子系统功能与安装部署的详细介绍,请参考[WeBASE在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/index.html) ## 各子系统简介 * **节点前置服务 [WeBASE-Front](https://github.com/WeBankFinTech/WeBASE-Front)** diff --git a/deploy/README-en.md b/deploy/README-en.md index 80e5b9b..d1fcee3 100644 --- a/deploy/README-en.md +++ b/deploy/README-en.md @@ -1,7 +1,7 @@ # Quick build ## Introduction -One-click deployment quickly builds A WeBASE management desk environment. Includes node (FISCO-BCOS 2.0), node front subsystem (WeBASE-Front), node management subsystem (WeBASE-Node-Manager), management platform (WeBASE-Web). Among them, the configuration of the node is optional, you can choose to use the existing chain or build a new chain. For more information, check out the online documentation for one-click deployment (https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html). +One-click deployment quickly builds A WeBASE management desk environment. Includes node (FISCO-BCOS 2.0), node front subsystem (WeBASE-Front), private key and sign managerment subsystem (WeBASE-Sign), node management subsystem (WeBASE-Node-Manager), management platform (WeBASE-Web). Among them, the configuration of the node is optional, you can choose to use the existing chain or build a new chain. For more information, check out the online documentation for one-click deployment (https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html). ## A description of the contribution Read our contribution documentation to learn how to contribute code and submit your contribution. diff --git a/deploy/install.md b/deploy/install.md index 2342639..9dcd8eb 100644 --- a/deploy/install.md +++ b/deploy/install.md @@ -1,7 +1,7 @@ # 快速搭建 ## 简介 -一键部署可以快速搭建WeBASE管理台环境。包括节点(FISCO-BCOS 2.0+)、节点前置子系统(WeBASE-Front)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。其中,节点的搭建是可选的,可以通过配置来选择使用已有链或者搭建新链。详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html)。 +一键部署可以快速搭建WeBASE管理台环境。包括节点(FISCO-BCOS 2.0+)、节点前置子系统(WeBASE-Front)、签名服务子系统(WeBASE-Sign)、节点管理子系统(WeBASE-Node-Manager)、管理平台(WeBASE-Web)。其中,节点的搭建是可选的,可以通过配置来选择使用已有链或者搭建新链。详细介绍请查看[一键部署在线文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html)。 ## 贡献说明 请阅读我们的贡献文档,了解如何贡献代码,并提交你的贡献。 From f60200bb5ddc786e567f3c00e4bbc4eee3f95e9c Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 12 Mar 2020 15:30:33 +0800 Subject: [PATCH 097/119] fix version & add webase-sign --- deploy/comm/build.py | 115 +++++++++++++++++++++++++++++++++++---- deploy/comm/check.py | 32 +++++++++-- deploy/comm/mysql.py | 40 ++++++++++++++ deploy/common.properties | 20 +++++-- 4 files changed, 189 insertions(+), 18 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 577c6f1..0b07864 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -14,11 +14,19 @@ def do(): installNode() installWeb() installManager() + installSign() installFront() print ("===================== deploy end... =====================") os.chdir(currentDir) - version = getCommProperties("webase.version") - print ("===================== version {} =====================".format(version)) + web_version = getCommProperties("webase.web.version") + mgr_version = getCommProperties("webase.mgr.version") + sign_version = getCommProperties("webase.sign.version") + front_version = getCommProperties("webase.front.version") + + print ("===================== webase-web version {} =====================".format(web_version)) + print ("===================== webase-node-mgr version {} =====================".format(mgr_version)) + print ("===================== webase-sign version {} =====================".format(sign_version)) + print ("===================== webase-front version {} =====================".format(front_version)) print ("================================================================") return @@ -26,6 +34,7 @@ def start(): startNode() startWeb() startManager() + startSign() startFront() return @@ -33,6 +42,7 @@ def end(): stopNode() stopWeb() stopManager() + stopSign() stopFront() return @@ -145,8 +155,8 @@ def installWeb(): print ("================================================================") print ("============== WeBASE-Web install... ==============") os.chdir(currentDir) - version = getCommProperties("webase.version") - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(version) + web_version = getCommProperties("webase.web.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-web.zip".format(web_version) pullSourceExtract(gitComm,"webase-web") changeWebConfig() startWeb() @@ -251,9 +261,9 @@ def installManager(): print ("================================================================") print ("============== WeBASE-Node-Manager install... ==============") os.chdir(currentDir) - version = getCommProperties("webase.version") + mgr_version = getCommProperties("webase.mgr.version") encrypt_type = int(getCommProperties("encrypt.type")) - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(version) + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-node-mgr.zip".format(mgr_version) pullSourceExtract(gitComm,"webase-node-mgr") changeManagerConfig() dbConnect() @@ -297,7 +307,7 @@ def installManager(): def startManager(): print ("============== WeBASE-Node-Manager start... ==============") os.chdir(currentDir) - managerPort = getCommProperties("front.port") + managerPort = getCommProperties("mgr.port") server_dir = currentDir + "/webase-node-mgr" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") @@ -381,12 +391,12 @@ def installFront(): print ("================================================================") print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) - version = getCommProperties("webase.version") + front_version = getCommProperties("webase.front.version") encrypt_type = int(getCommProperties("encrypt.type")) - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(version) + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(front_version) frontPackage = "webase-front" if encrypt_type == 1: - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front-gm.zip".format(version) + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front-gm.zip".format(front_version) frontPackage = "webase-front-gm" server_dir = currentDir + "/" + frontPackage pullSourceExtract(gitComm,frontPackage) @@ -473,3 +483,88 @@ def stopFront(): else: print ("======= WeBASE-Front stop fail. Please view log file (default path:./log/). =======") return + +def changeSignConfig(): + # get properties + sign_port = getCommProperties("sign.port") + mysql_ip = getCommProperties("sign.mysql.ip") + mysql_port = getCommProperties("sign.mysql.port") + mysql_user = getCommProperties("sign.mysql.user") + mysql_password = getCommProperties("sign.mysql.password") + mysql_database = getCommProperties("sign.mysql.database") + + # init file + server_dir = currentDir + "/webase-sign" + conf_dir = server_dir + "/conf" + if not os.path.exists(conf_dir + "/temp.yml"): + doCmd('cp -f {}/application.yml {}/temp.yml'.format(conf_dir, conf_dir)) + else: + doCmd('cp -f {}/temp.yml {}/application.yml'.format(conf_dir, conf_dir)) + + # change server config + doCmd('sed -i "s/5004/{}/g" {}/application.yml'.format(sign_port, conf_dir)) + doCmd('sed -i "s/127.0.0.1/{}/g" {}/application.yml'.format(mysql_ip, conf_dir)) + doCmd('sed -i "s/3306/{}/g" {}/application.yml'.format(mysql_port, conf_dir)) + doCmd('sed -i "s/defaultAccount/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) + doCmd('sed -i "s/defaultPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) + doCmd('sed -i "s/webasenodesign/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) + + return + +def installSign(): + print ("================================================================") + print ("============== WeBASE-Sign install... ==============") + os.chdir(currentDir) + sign_version = getCommProperties("webase.sign.version") + gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-sign.zip".format(sign_version) + # test pull sign url, put webase-sign.zip + pullSourceExtract(gitComm,"webase-sign") + changeSignConfig() + signDbConnect() + startSign() + return + +def startSign(): + print ("============== WeBASE-Sign start... ==============") + os.chdir(currentDir) + signPort = getCommProperties("sign.port") + server_dir = currentDir + "/webase-sign" + os.chdir(server_dir) + doCmdIgnoreException("source /etc/profile") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result = doCmd("bash start.sh") + if result["status"] == 0: + if_started = 'is running' in result["output"] + if if_started: + pid = get_str_btw(result["output"], "(", ")") + print ("WeBASE-Sign Port {} is running PID({})".format(signPort,pid)) + sys.exit(0) + if_success = 'Starting' in result["output"] + if if_success: + print ("======= WeBASE-Sign starting. Please check through the log file (default path:./webase-sign/log/). =======") + else: + print ("======= WeBASE-Sign start fail. Please check through the log file (default path:./webase-sign/log/). =======") + sys.exit(0) + else: + print ("======= WeBASE-Sign start fail. Please view log file (default path:./log/). =======") + sys.exit(0) + print ("============== WeBASE-Sign end... ==============") + return + +def stopSign(): + server_dir = currentDir + "/webase-sign" + os.chdir(server_dir) + doCmdIgnoreException("source /etc/profile") + doCmdIgnoreException("chmod u+x *.sh") + doCmdIgnoreException("dos2unix *.sh") + result = doCmd("bash stop.sh") + if result["status"] == 0: + if_success = 'Success' in result["output"] + if if_success: + print ("======= WeBASE-Sign stop success! =======") + else: + print ("======= WeBASE-Sign is not running! =======") + else: + print ("======= WeBASE-Sign stop fail. Please view log file (default path:./log/). =======") + return \ No newline at end of file diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 8f356bb..60afff3 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -27,14 +27,17 @@ def do(): checkNodePort() checkWebPort() checkMgrPort() - checkFrontPort() - checkDbConnect() + checkSignPort() + checkFrontPort() + checkSignDbConnect() + checkMgrDbConnect() print ("===================== envrionment ready... =====================") print ("================================================================") def checkPort(): checkWebPort() checkMgrPort() + checkSignPort() checkFrontPort() def installRequirements(): @@ -149,14 +152,35 @@ def checkFrontPort(): sys.exit(0) print ("check finished sucessfully.") return + +def checkSignPort(): + print ("check WeBASE-Sign port...") + deploy_ip = "127.0.0.1" + sign_port = getCommProperties("sign.port") + res_sign = net_if_used(deploy_ip,sign_port) + if res_sign: + sys.exit(0) + print ("check finished sucessfully.") + return -def checkDbConnect(): +def checkMgrDbConnect(): print ("check database connection...") mysql_ip = getCommProperties("mysql.ip") mysql_port = getCommProperties("mysql.port") ifLink = do_telnet(mysql_ip,mysql_port) if not ifLink: - print ('The database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port)) + print ('Mgr database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port)) + sys.exit(0) + print ("check finished sucessfully.") + return + +def checkSignDbConnect(): + print ("check database connection...") + mysql_ip = getCommProperties("sign.mysql.ip") + mysql_port = getCommProperties("sign.mysql.port") + ifLink = do_telnet(mysql_ip,mysql_port) + if not ifLink: + print ('Sign database ip:{} port:{} is disconnected, please confirm.'.format(mysql_ip, mysql_port)) sys.exit(0) print ("check finished sucessfully.") return diff --git a/deploy/comm/mysql.py b/deploy/comm/mysql.py index f36a7d8..1ffbfbd 100644 --- a/deploy/comm/mysql.py +++ b/deploy/comm/mysql.py @@ -47,6 +47,46 @@ def dbConnect(): log.info(" mysql except {}".format(traceback.format_exc())) traceback.print_exc() sys.exit(0) + +def signDbConnect(): + # get properties + mysql_ip = getCommProperties("sign.mysql.ip") + mysql_port = int(getCommProperties("sign.mysql.port")) + mysql_user = getCommProperties("sign.mysql.user") + mysql_password = getCommProperties("sign.mysql.password") + mysql_database = getCommProperties("sign.mysql.database") + + try: + # connect + conn = mdb.connect(host=mysql_ip, port=mysql_port, user=mysql_user, passwd=mysql_password, charset='utf8') + conn.autocommit(1) + cursor = conn.cursor() + + # check db + result = cursor.execute('show databases like "%s"' %mysql_database) + drop_db = 'DROP DATABASE IF EXISTS {}'.format(mysql_database) + create_db = 'CREATE DATABASE IF NOT EXISTS {}'.format(mysql_database) + if result == 1: + info = "n" + if sys.version_info.major == 2: + info = raw_input("WeBASE-Sign database {} already exists, delete rebuild or not?[y/n]:".format(mysql_database)) + else: + info = input("WeBASE-Sign database {} already exists, delete rebuild or not?[y/n]:".format(mysql_database)) + if info == "y" or info == "Y": + log.info(drop_db) + cursor.execute(drop_db) + log.info(create_db) + cursor.execute(create_db) + else: + log.info(create_db) + cursor.execute(create_db) + cursor.close() + conn.close() + except: + import traceback + log.info(" mysql except {}".format(traceback.format_exc())) + traceback.print_exc() + sys.exit(0) if __name__ == '__main__': pass diff --git a/deploy/common.properties b/deploy/common.properties index c90e0bf..df59067 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,24 +1,36 @@ [common] -# Webase Version (V1.1.0 or above) -webase.version=v1.2.2 +# Webase Version (v1.1.0 or above) +webase.web.version=v1.3.0 +webase.mgr.version=v1.3.0 +webase.sign.version=v1.3.0 +webase.front.version=v1.3.0 -# Mysql database configuration of WeBASE-NodeManager +# Mysql database configuration of WeBASE-Node-Manager mysql.ip=localhost mysql.port=3306 mysql.user=dbUsername mysql.password=dbPassword mysql.database=webasenodemanager +# Mysql database configuration of WeBASE-Sign +sign.mysql.ip=localhost +sign.mysql.port=3306 +sign.mysql.user=dbUsername +sign.mysql.password=dbPassword +sign.mysql.database=webasenodesign + # H2 database name of WeBASE-Front front.h2.name=webasefront # WeBASE-Web service port web.port=5000 -# WeBASE-NodeManager service port +# WeBASE-Node-Manager service port mgr.port=5001 # WeBASE-Front service port front.port=5002 +# WeBASE-Sign service port +sign.port=5004 # Node listening IP node.listenIp=127.0.0.1 From 8a0acf12b289ae16889173a10c31808120a1abb1 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 12 Mar 2020 16:40:37 +0800 Subject: [PATCH 098/119] fix mysql config & import --- deploy/comm/build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 0b07864..333d9d9 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -4,7 +4,7 @@ import sys import os from .utils import * -from .mysql import dbConnect +from .mysql import * baseDir = getBaseDir() currentDir = getCurrentBaseDir() @@ -505,8 +505,8 @@ def changeSignConfig(): doCmd('sed -i "s/5004/{}/g" {}/application.yml'.format(sign_port, conf_dir)) doCmd('sed -i "s/127.0.0.1/{}/g" {}/application.yml'.format(mysql_ip, conf_dir)) doCmd('sed -i "s/3306/{}/g" {}/application.yml'.format(mysql_port, conf_dir)) - doCmd('sed -i "s/defaultAccount/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) - doCmd('sed -i "s/defaultPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) + doCmd('sed -i "s/dbUsername/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) + doCmd('sed -i "s/dbPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) doCmd('sed -i "s/webasenodesign/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) return From f913f7b68e02661e6eeeb4830361aaffbf6d5e70 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 12 Mar 2020 16:49:21 +0800 Subject: [PATCH 099/119] fix sign db name --- deploy/comm/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 333d9d9..758c304 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -507,7 +507,7 @@ def changeSignConfig(): doCmd('sed -i "s/3306/{}/g" {}/application.yml'.format(mysql_port, conf_dir)) doCmd('sed -i "s/dbUsername/{}/g" {}/application.yml'.format(mysql_user, conf_dir)) doCmd('sed -i "s/dbPassword/{}/g" {}/application.yml'.format(mysql_password, conf_dir)) - doCmd('sed -i "s/webasenodesign/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) + doCmd('sed -i "s/webasesign/{}/g" {}/application.yml'.format(mysql_database, conf_dir)) return From aaa86848657c59415870a015112c3224d09a852d Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 12 Mar 2020 17:52:37 +0800 Subject: [PATCH 100/119] update fisco 2.2.0 --- deploy/common.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/common.properties b/deploy/common.properties index df59067..1226a72 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -57,6 +57,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.1.0 +fisco.version=2.2.0 # Number of building nodes (two by default) node.counts=nodeCounts \ No newline at end of file From 7ef08f4aeb1b5f50d821323dea7538952987e761 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Fri, 13 Mar 2020 11:48:24 +0800 Subject: [PATCH 101/119] update fisco 2.2.0 --- deploy/common.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index c9056ae..675218d 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,6 +1,6 @@ [common] -# Webase Version (V1.1.0 or above) +# Webase Subsystem Version (V1.1.0 or above) webase.web.version=v1.2.2 webase.mgr.version=v1.2.2 webase.front.version=v1.2.3 @@ -47,6 +47,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.1.0 +fisco.version=2.2.0 # Number of building nodes (two by default) node.counts=nodeCounts \ No newline at end of file From f055b35a822aedffd78d6c733e179e167383f0c0 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Mon, 23 Mar 2020 11:54:29 +0800 Subject: [PATCH 102/119] add webase-sign in one_click_install --- deploy/comm/build.py | 1 + deploy/common.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 758c304..3aa3279 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -68,6 +68,7 @@ def installNode(): if node_counts != "nodeCounts": node_nums = int(node_counts) doCmd('sed -i "s/nodeCounts/{}/g" nodeconf'.format(node_nums)) + doCmdIgnoreException("dos2unix nodeconf") gitComm = "wget https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v{}/build_chain.sh && chmod u+x build_chain.sh".format(fisco_version) if not os.path.exists("{}/nodes".format(currentDir)): diff --git a/deploy/common.properties b/deploy/common.properties index 1226a72..8be1d73 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,6 +1,6 @@ [common] -# Webase Version (v1.1.0 or above) +# Webase Subsystem Version (v1.1.0 or above) webase.web.version=v1.3.0 webase.mgr.version=v1.3.0 webase.sign.version=v1.3.0 From c182ab45b5562b0f15910e3558b721dab1b083e1 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Mon, 23 Mar 2020 16:51:46 +0800 Subject: [PATCH 103/119] fix style of duplicated whitespace --- deploy/comm/check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/comm/check.py b/deploy/comm/check.py index 60afff3..ea7ec41 100644 --- a/deploy/comm/check.py +++ b/deploy/comm/check.py @@ -28,7 +28,7 @@ def do(): checkWebPort() checkMgrPort() checkSignPort() - checkFrontPort() + checkFrontPort() checkSignDbConnect() checkMgrDbConnect() print ("===================== envrionment ready... =====================") From 008a3335917db44e7a75bf091d6eb1f72d394ae2 Mon Sep 17 00:00:00 2001 From: marsli Date: Mon, 13 Apr 2020 11:19:58 +0800 Subject: [PATCH 104/119] update for 1.2.4 --- deploy/common.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index 675218d..84c32ff 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,9 +1,9 @@ [common] -# Webase Subsystem Version (V1.1.0 or above) -webase.web.version=v1.2.2 -webase.mgr.version=v1.2.2 -webase.front.version=v1.2.3 +# Webase Subsystem Version (v1.1.0 or above) +webase.web.version=v1.2.4 +webase.mgr.version=v1.2.4 +webase.front.version=v1.2.4 # Mysql database configuration of WeBASE-NodeManager mysql.ip=localhost @@ -49,4 +49,4 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Fisco-bcos version fisco.version=2.2.0 # Number of building nodes (two by default) -node.counts=nodeCounts \ No newline at end of file +node.counts=nodeCounts From d2843b095f39cc86afda10140c5e1f8d6a8d7d4a Mon Sep 17 00:00:00 2001 From: marsli Date: Tue, 14 Apr 2020 11:18:37 +0800 Subject: [PATCH 105/119] update fisco 2.3.0 --- deploy/common.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/common.properties b/deploy/common.properties index 84c32ff..b93aa50 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -47,6 +47,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.2.0 +fisco.version=2.3.0 # Number of building nodes (two by default) node.counts=nodeCounts From eaeaef4db66a448c4546a639818ec4525b8a6079 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Wed, 15 Apr 2020 17:39:33 +0800 Subject: [PATCH 106/119] fix h2 path --- deploy/comm/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index c557205..ad4bf93 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -376,7 +376,7 @@ def changeFrontConfig(): doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s%encryptType: 0%encryptType: {}%g" {}/application.yml'.format(encrypt_type, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) - doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) + doCmd('sed -i "s%/webasefront%/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) doCmd('sed -i "s%nodePath: /fisco/nodes/127.0.0.1/node0%nodePath: {}%g" {}/application.yml'.format(node_dir, server_dir)) From aec17f9b8f4c270a4aaa11965953b0df5df1d7a8 Mon Sep 17 00:00:00 2001 From: marsli Date: Thu, 7 May 2020 11:45:17 +0800 Subject: [PATCH 107/119] update fisco 2.4.0 --- deploy/common.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index 1226a72..8548c54 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -57,6 +57,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.2.0 +fisco.version=2.4.0 # Number of building nodes (two by default) -node.counts=nodeCounts \ No newline at end of file +node.counts=nodeCounts From 163ea7aa4a7bdf667c26c5c42ffdcb8fdd737ecf Mon Sep 17 00:00:00 2001 From: marsli Date: Thu, 7 May 2020 12:31:17 +0800 Subject: [PATCH 108/119] rm comments --- deploy/comm/build.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 758c304..f6ea527 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -517,7 +517,6 @@ def installSign(): os.chdir(currentDir) sign_version = getCommProperties("webase.sign.version") gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-sign.zip".format(sign_version) - # test pull sign url, put webase-sign.zip pullSourceExtract(gitComm,"webase-sign") changeSignConfig() signDbConnect() @@ -567,4 +566,4 @@ def stopSign(): print ("======= WeBASE-Sign is not running! =======") else: print ("======= WeBASE-Sign stop fail. Please view log file (default path:./log/). =======") - return \ No newline at end of file + return From 20de32ae33ae24ea0a1a76d2bb00c56daefc1852 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Fri, 8 May 2020 15:47:25 +0800 Subject: [PATCH 109/119] fix sign db name --- deploy/comm/build.py | 1 - deploy/common.properties | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index 3aa3279..c9aeb52 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -518,7 +518,6 @@ def installSign(): os.chdir(currentDir) sign_version = getCommProperties("webase.sign.version") gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-sign.zip".format(sign_version) - # test pull sign url, put webase-sign.zip pullSourceExtract(gitComm,"webase-sign") changeSignConfig() signDbConnect() diff --git a/deploy/common.properties b/deploy/common.properties index 8be1d73..1686de2 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -18,7 +18,7 @@ sign.mysql.ip=localhost sign.mysql.port=3306 sign.mysql.user=dbUsername sign.mysql.password=dbPassword -sign.mysql.database=webasenodesign +sign.mysql.database=webasesign # H2 database name of WeBASE-Front front.h2.name=webasefront @@ -57,6 +57,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.2.0 +fisco.version=2.4.0 # Number of building nodes (two by default) node.counts=nodeCounts \ No newline at end of file From 2354dd5254d9b432d1a69b226755a5917d9bd664 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Tue, 26 May 2020 18:16:43 +0800 Subject: [PATCH 110/119] add start/stop sign --- deploy/deploy.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deploy/deploy.py b/deploy/deploy.py index 5ad747b..27186c5 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -33,6 +33,10 @@ def do(): commBuild.startFront() elif "stopFront" == param: commBuild.stopFront() + elif "startFront" == param: + commBuild.startSign() + elif "stopFront" == param: + commBuild.stopSign() elif "check"== param: commCheck.do() elif "help"== param: @@ -58,7 +62,9 @@ def help(): stopManager: stop WeBASE-Node-Manager server startFront: start WeBASE-Front server stopFront: stop WeBASE-Front server - + startSign: start WeBASE-Sign server + stopSign: stop WeBASE-Sign server + Attention: 1. Need to install python, jdk, mysql, MySQL-python or PyMySQL first 2. Need to ensure a smooth network From 806fe1c6515e5dd8c0b33acececbb278ea3cd694 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 4 Jun 2020 15:42:05 +0800 Subject: [PATCH 111/119] update version & update front auto switch socJ --- deploy/comm/build.py | 12 ------------ deploy/common.properties | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index d7be09d..cd05998 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -369,8 +369,6 @@ def changeFrontConfig(): # init file server_dir = currentDir + "/webase-front/conf" - if encrypt_type == 1: - server_dir = currentDir + "/webase-front-gm/conf" if not os.path.exists(server_dir + "/temp.yml"): doCmd('cp -f {}/application.yml {}/temp.yml'.format(server_dir, server_dir)) else: @@ -393,12 +391,8 @@ def installFront(): print ("============== WeBASE-Front install... ==============") os.chdir(currentDir) front_version = getCommProperties("webase.front.version") - encrypt_type = int(getCommProperties("encrypt.type")) gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front.zip".format(front_version) frontPackage = "webase-front" - if encrypt_type == 1: - gitComm = "wget https://www.fisco.com.cn/cdn/webase/releases/download/{}/webase-front-gm.zip".format(front_version) - frontPackage = "webase-front-gm" server_dir = currentDir + "/" + frontPackage pullSourceExtract(gitComm,frontPackage) changeFrontConfig() @@ -436,10 +430,7 @@ def startFront(): print ("============== WeBASE-Front start... ==============") os.chdir(currentDir) frontPort = getCommProperties("front.port") - encrypt_type = int(getCommProperties("encrypt.type")) frontPackage = "webase-front" - if encrypt_type == 1: - frontPackage = "webase-front-gm" os.chdir(currentDir + "/" + frontPackage) doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") @@ -466,10 +457,7 @@ def startFront(): def stopFront(): os.chdir(currentDir) - encrypt_type = int(getCommProperties("encrypt.type")) server_dir = currentDir + "/webase-front" - if encrypt_type == 1: - server_dir = currentDir + "/webase-front-gm" os.chdir(server_dir) doCmdIgnoreException("source /etc/profile") doCmdIgnoreException("chmod u+x *.sh") diff --git a/deploy/common.properties b/deploy/common.properties index 31e02d2..df6f903 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,10 +1,10 @@ [common] # Webase Subsystem Version (v1.1.0 or above) -webase.web.version=v1.3.0 -webase.mgr.version=v1.3.0 -webase.sign.version=v1.3.0 -webase.front.version=v1.3.0 +webase.web.version=v1.3.1 +webase.mgr.version=v1.3.1 +webase.sign.version=v1.3.1 +webase.front.version=v1.3.1 # Mysql database configuration of WeBASE-Node-Manager mysql.ip=localhost From bcf8b9d65b9d8144c7b2c8de5c485a0636ea97ce Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Tue, 9 Jun 2020 10:48:29 +0800 Subject: [PATCH 112/119] fix start sign param --- deploy/deploy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/deploy.py b/deploy/deploy.py index 27186c5..960b22d 100644 --- a/deploy/deploy.py +++ b/deploy/deploy.py @@ -33,9 +33,9 @@ def do(): commBuild.startFront() elif "stopFront" == param: commBuild.stopFront() - elif "startFront" == param: + elif "startSign" == param: commBuild.startSign() - elif "stopFront" == param: + elif "stopSign" == param: commBuild.stopSign() elif "check"== param: commCheck.do() From 9cba5b603af6bca3b0fb8b07f076bbf4973a27a9 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Wed, 10 Jun 2020 10:51:32 +0800 Subject: [PATCH 113/119] fix front sign port --- deploy/comm/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index cd05998..eb81a05 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -353,7 +353,7 @@ def stopManager(): def changeFrontConfig(): # get properties deploy_ip = "127.0.0.1" - mgr_port = getCommProperties("mgr.port") + sign_port = getCommProperties("sign.port") frontPort = getCommProperties("front.port") nodeListenIp = getCommProperties("node.listenIp") nodeChannelPort = getCommProperties("node.channelPort") @@ -379,7 +379,7 @@ def changeFrontConfig(): doCmd('sed -i "s/ip: 127.0.0.1/ip: {}/g" {}/application.yml'.format(nodeListenIp, server_dir)) doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s%encryptType: 0%encryptType: {}%g" {}/application.yml'.format(encrypt_type, server_dir)) - doCmd('sed -i "s/keyServer: 127.0.0.1:5001/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, mgr_port, server_dir)) + doCmd('sed -i "s/keyServer: 127.0.0.1:5004/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, sign_port, server_dir)) doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) doCmd('sed -i "s%nodePath: /fisco/nodes/127.0.0.1/node0%nodePath: {}%g" {}/application.yml'.format(node_dir, server_dir)) From 90178cd63943b8ed5f360657b268ae3b864474c0 Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Wed, 10 Jun 2020 10:56:44 +0800 Subject: [PATCH 114/119] upgrade fisco 2.4.1 --- deploy/common.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/common.properties b/deploy/common.properties index df6f903..5423281 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -57,6 +57,6 @@ node.dir=/data/app/nodes/127.0.0.1/node0 # Configuration required when building a new chain # Fisco-bcos version -fisco.version=2.4.0 +fisco.version=2.4.1 # Number of building nodes (two by default) node.counts=nodeCounts From 04ef29ade52c8fb0507cc7dd19b5953ab82dad2d Mon Sep 17 00:00:00 2001 From: CodingCattwo <847701726@qq.com> Date: Thu, 11 Jun 2020 11:48:10 +0800 Subject: [PATCH 115/119] simplify h2 path --- deploy/comm/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/comm/build.py b/deploy/comm/build.py index eb81a05..1a7402d 100644 --- a/deploy/comm/build.py +++ b/deploy/comm/build.py @@ -380,7 +380,7 @@ def changeFrontConfig(): doCmd('sed -i "s/20200/{}/g" {}/application.yml'.format(nodeChannelPort, server_dir)) doCmd('sed -i "s%encryptType: 0%encryptType: {}%g" {}/application.yml'.format(encrypt_type, server_dir)) doCmd('sed -i "s/keyServer: 127.0.0.1:5004/keyServer: {}:{}/g" {}/application.yml'.format(deploy_ip, sign_port, server_dir)) - doCmd('sed -i "s%./h2/webasefront%../h2/{}%g" {}/application.yml'.format(frontDb, server_dir)) + doCmd('sed -i "s%/webasefront%/{}%g" {}/application.yml'.format(frontDb, server_dir)) doCmd('sed -i "s%monitorDisk: /%monitorDisk: {}%g" {}/application.yml'.format(fisco_dir, server_dir)) doCmd('sed -i "s%nodePath: /fisco/nodes/127.0.0.1/node0%nodePath: {}%g" {}/application.yml'.format(node_dir, server_dir)) From 05485e71b75b9547c55c2e48f5c3dd824b4c136d Mon Sep 17 00:00:00 2001 From: marsli Date: Mon, 22 Jun 2020 17:19:59 +0800 Subject: [PATCH 116/119] update 1.3.2 --- deploy/common.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/common.properties b/deploy/common.properties index 5423281..8372f73 100644 --- a/deploy/common.properties +++ b/deploy/common.properties @@ -1,10 +1,10 @@ [common] # Webase Subsystem Version (v1.1.0 or above) -webase.web.version=v1.3.1 -webase.mgr.version=v1.3.1 -webase.sign.version=v1.3.1 -webase.front.version=v1.3.1 +webase.web.version=v1.3.2 +webase.mgr.version=v1.3.2 +webase.sign.version=v1.3.2 +webase.front.version=v1.3.2 # Mysql database configuration of WeBASE-Node-Manager mysql.ip=localhost From 8d9a2ea22b229b76c5531a0d9cc8cea76c7c6679 Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Tue, 7 Jul 2020 10:56:10 +0800 Subject: [PATCH 117/119] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 73a5655..54ce59c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,15 @@ * **数据导出服务 [WeBASE-Collect-Bee](https://github.com/WeBankFinTech/WeBASE-Collect-Bee)** 导出区块链上的基础数据,如当前块高、交易总量等,通过智能合约的配置,导出区块链上合约的业务数据,包括event、构造函数、合约地址、执行函数的信息等。 +* **链管理服务 [WeBASE-Chain-Manager](https://github.com/WeBankFinTech/WeBASE-Chain-Manager)** +链管理服务支持管理多条链,支持国密链、非国密链。对外提供群组的增删查改接口,让用户可以便捷地建立自己应用的群组。 + +* **合约安全检测服务 [WeBASE-Solidity-Security](https://github.com/WeBankFinTech/WeBASE-Solidity-Security)** +合约安全检测服务继承了solidity合约检测工具slither,对外提供检测接口。 + +* **数据统计服务 [WeBASE-Stat](https://github.com/WeBankFinTech/WeBASE-Stat)** +统计数据服务以前置为基础,拉取CPU、内存、IO、群组大小、群组gas、群组网络流量的数据,记录数据库。 + ## 贡献说明 请阅读我们的[贡献文档](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/CONTRIBUTING.html),了解如何贡献代码,并提交你的贡献。 From ca425aafaac067b3bb9984487b702b7964d5449a Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Tue, 7 Jul 2020 10:56:21 +0800 Subject: [PATCH 118/119] Update README.md From 46342a1cabc3628fd2f8f0f2ae60eed83f3312bf Mon Sep 17 00:00:00 2001 From: mingzhenliu <42059349@qq.com> Date: Tue, 7 Jul 2020 11:45:43 +0800 Subject: [PATCH 119/119] update --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 2ae2251..b85972e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.sh linguist-language=JAVA +*.py linguist-language=JAVA