Golang으로 Synology 연동 Telegram 봇 만들기

재택근무를 하면서 q10에서 타오 나스를 구매했습니다. 주 용도는 마크 서버 및 torrent 머신인데, 헤놀로지 세팅을 하다 보니 욕심이 생겨서 외부에서도 텔레그램 봇을 통해 torrent파일이나 마그넷 링크로 다운로드할 수 있게 했습니다. 여기서는 그 방법을 공유하려고 합니다.

 

목표

  • Telegram bot: 예전에 python으로는 만들어본 적이 있는데, 이번에는 Golang으로 만들어 보려고 합니다. Torrent 파일은 disk station의 감시 경로에 넣어주면 되고, magnet은 Synology API(SYNO API)에서 지원하네요.
  • 시놀로지 세팅: Download Station 감시 설정, API 설정, API 연동용 계정 생성 등을 해야 합니다. 그리고 시놀로지에서는 Golang을 지원하지 않아서 Docker를 사용하려고 합니다.

 

Telegram bot

  1. Telegram bot을 먼저 생생해야 합니다. 봇 생성 방법은 검색해보면 많이 나오므로 여기서는 설명하지 않습니다.

https://core.telegram.org/bots#6-botfather

  • 봇을 생성하고 토큰값을 사용해야 하므로 기록해둡니다.

 

  1. 봇과 연동할 App 소스 코드는 아래 경로에서 다운로드합니다.

https://github.com/lmk/tgbot-ds

 

시놀로지 세팅

 

  1. 공유 폴더 생성:
  • Download Station에서 감시할 'watch'라는 새 공유 폴더를 생성합니다.
  • Telegram bot 소스 코드를 넣을 'system/tgbot-ds' 폴더를 생성합니다.

     

  1. Synology API 연동용 계정 생성
  • 'tt_api'라는 계정을 만들고 watch 폴더의 읽고/쓰기 권한을 줍니다.
  • Synology API에서 연동할 수 있도록 Download Station 응용프로그램 허용 권한을 줍니다.
  • 계정을 생성했으면 현재 계정을 로그 아웃하고, 생성한 계정으로 로그인을 해봅니다.

 

  1. Download Station 감시 설정
  • Download Station 설정에서 Torrent 파일 감시할 수 있도록 합니다.

     

  1. Synology API 설정
  • 제어판 > 응용 프로그램 포털에서 'Download Station'을 선택하고 [편집] 버튼을 눌러 사용자 지정 포트를 활성화합니다.

     

  1. Docker 설정
    • Docker 설치가 안 되어 있으면, 패키지 센터에서 Docker를 먼저 설치합니다.
    • Docker를 열어서 '이미지' 메뉴 > [추가] 버튼 > URL에서 추가 > 'golang' 입력 > '1-alpine' 태그 선택을 합니다.
    • 잠시 후 이미지가 생성되면 이미지를 선택하고 [실행] 버튼을 누릅니다.
    • 컨테이너 생성 화면에서 [고급 설정] 버튼을 누릅니다.
    • 고급 설정 창 > 고급 설정 > 자동 재시작 활성화를 누릅니다.
    • 컨테이너가 폴더 접근을 할 수 있도록 볼륨을 설정합니다.
    • App에서 환경 변수를 추가합니다.

    • BOTTOKEN에는 봇을 생성할 때 기록해둔 토큰 값을 입력합니다.
    • MYTGNAME에는 봇과 대화할 내 텔레그램 이름입니다.
    • DESTINATION에는 볼륨에서 마운트한 '/watch' 경로입니다. 앞에 "/"가 있어야 합니다.
    • SYNOHOST는 Synology API 설정에서 지정한 사용자 지정 URL과 포트입니다.
    • SYNID는 위에서 생성한 Synology API 연동 ID입니다.
    • SYNPWD는 위에서 생성한 Synology API 연동 ID의 암호입니다.
    • SYNOPATH는 Download Station에서 감시할 기놀로지 기준의 경로입니다. 앞에 "/"가 없어야 합니다.
  • 명령에는 /bin/sh /go/src/run.sh를 입력합니다.
  • 이렇게 하면 시놀에서 Golang을 실행할 수 있는 환경이 만들어집니다.

 

테스트

  1. Docker > 컨테이너 메뉴에서 생성한 Golang을 실행해주고, [세부사항] 버튼을 눌러 창을 엽니다.

     

  2. '로그'탭으로 이동해서 로그를 확인합니다. 아래와 같은 로그가 올라오면 준비가 된 것입니다.
  3. Telegram에서 봇 에게 Torrent 파일을 전송하거나, magent URI를 메시지로 보내면 Download Station에서 다운이 됩니다.

 

사족, 

SYNO API 연동하는데, 이것저것 테스트해보느라 시간이 좀 오려 걸렸네요. 그리고, Golang으로 만들려는 욕심 때문에 Docker를 사용해야 했고, 이 과정에서 외부 패키지 연동이 안돼서 한참을 고생했네요. 결국엔 외부 패키지 전체를 복사해서 실행에 성공을 했네요. 이 부분이 깔끔하지 못해 아쉽습니다. 크로스 컴파일을 해서 바이너리를 직접 올릴까도 생각해봤는데, 시놀과 헤놀 모두를 지원하려면 직접 실행하는 방법이 나을 것 같아서 진행했습니다.

jmeter를 사용한 가변길이 TCP Binary 패킷 처리 방법

Use Dynamic Binary Packet for JMeter

성능 테스트를 위해 가장 많이 사용된다는 Tool인 JMeter을 사용하기로 했습니다.

JMeter는 HTTP, HTTPS, FTP, REST, TCP등 다양한 프로토콜을 지원한다고 하는데, TCP Binary 패킷에 대한 송수신 처리 자료는 찾기 힘들었습니다. 여기서 JMeter를 사용한 가변길이 TCP Binary 패킷 처리를 설명합니다.

JMeter의 beanShell을 사용해서 송수신 패킷을 만들고, 별도 제작한 plugin을 사용해 TCP 송수신 처리를 할 것입니다.

Test 시나리오

  • 테스트할 서버는 로컬에 1234 port를 listen하고 있습니다.
  • 요청 패킷에 요청 seq가 포함됩니다.
  • 요청/응답 header 패킷은 동일합니다.
struct header_ {
    unsigned char version;      // 1 byte
    short int seq;              // 2 byte
    unsigned char str;          // 1 byte
    short int type;             // 2 byte
    short int bodyLen;          // 2 byte
}
  • body는 일반 문자열입니다.
    • 요청 패킷 전문은 CSV 파일을 읽어 내용을 만듭니다.
  • thread(socket connection) 는 10개로 합니다.

Apache JMeter 소스 받기

JMeter의 sampler에서 tcp 가변길이 패킷을 처리할 수 있는 방법이 없으므로, plug-in을 만들어야 합니다. 

Java Request plug-in을 만들기 위해 JMeter 소스를 받습니다.

1. github 에서 fork

https://github.com/apache/jmeter

2. 내 PC에 checkout

git clone https://github.com/lmk/jmeter

3. branch 생성

git branch DynamicLengthTCP

4. Eclipse import 준비

ant setup-eclipse-project

5. Eclipse > File > Import > Existing Projects into Workspace

코드 추가

1. org.apache.jmeter.protocol.java.test 아래 DynamicLengthTCP, SingletonSocketMap class 추가

  • DynamicLengthTCP
    • AbstractJavaSamplerClient 상속받아 구현했습니다.
    • 자세한 내용은 JavaTest class 참고했습니다. (JavaTest를 그대로 복사해서 생성)
    • 접속할 서버 IP, 포트 등 plug-in에 옵션이 필요한데, getDefaultParamters()에 정의하면 JMeter에서 GUI까지 만들어 줍니다.
    • JMeter에서 생성한 Binary 패킷을 plug-in으로 가져오기 위해서는 hex string으로 변환해서 "RequestData"라는 변수로 넘어옵니다. (BinaryTCPClientImpl 참고) 
    • 저장된 요청 패킷을 byte[]로 변환하고(여기)  패킷을 전송합니다.
    • 응답 패킷 header 먼저 받고, body 길이만큼 응답 패킷을 받습니다.
    • 받은 Binary 패킷과 상태 정보를, JMeter에서 받을 수 있도록 SampleResult에 담습니다.
  • SingletonSocketMap
    • Thread별 Socket 재사용(Re-use Connected socket)을 위한 class 입니다.

2. git add 및 commit, push

Export jar

1. Package Explorer 에서 추가한 파일 선택

  • DynamicLengthTCP.java
  • SingletonSocketMap.java

2. Export > JAR File

3. \lib\ext 경로 아래 저장할 jar 파일명 입력

  • \lib\ext에 jar 파일을 넣어두면, JMeter 실행시 plugin으로 인식해서 읽어들입니다.
  • github에서 받은 소스의 \lib\ext 경로가 아니라, JMeter 실행파일이 위치한 경로입니다.
  • 제 경우는 소스는 "D:\src\jmeter"에 받았고, JMeter 실행파일은 "D:\Tools\apache-jmeter-5.0"에 받았으므로, jar 파일을 저장할 경로는 "D:\Tools\apache-jmeter-5.0\lib\ext" 였습니다.

Test

1. bin/jmeter.bat 실행

2. "Test Plan" 우클릭 > Add > Thread Group추가

- Number of Threads를 10 으로 수정합니다.
- Loop Count는 Forver 체크하면 Stop 할때까지 반복하지만, 테스트니까 기본값 1로 유지합니다.

3. "Thread Group" 우클릭 > Add > Config Element > Counter 추가.

  • 요청 패킷 seq 증가를 위한 것입니다.
  • 시작값, 증가값은 1로 설정합니다.
  • seq 는 패킷의 자료형 크기보다 작게 max 값을 정합니다.
    • 여기서는 short 형이므로 30000으로 잡았습니다.
  • Exported Variable Name에 jmeter 내부에서 사용할 변수명 "reqCount"를 입력합니다.

4. "Thread Group" 우클릭 > Add > Config Element > CSV Data Set Config 추가.

  • Filename에 "msg.txt"로 지정합니다.
    • msg.txt 파일 내용은 아와 같습니다.
    메시지1
    메시지2
    메시지3
    
    • 여기서는 row전체를 하나의 field를 사용합니다.
  • Variable Names에 JMeter 내부에서 사용할 변수명 "msg"를 입력합니다.
  • Recyle on EOF: row가 끝나면 처음부터 다시 읽도록 True로 설정합니다.
  • Stop thread on EOF: row가 끝나도 테스트를 계속 할것이므로 False로 설정합니다.

5. 테스트 결과를 쉽게 보기 위해 Listener 추가

  • "Thread Group" 우클릭 > Add > Listener > View Results Tree
  • "Thread Group" 우클릭 > Add > Listener > Summary Report
  • "Thread Group" 우클릭 > Add > Listener > Response Time Graph

6. "Thread Group" 우클릭 > Add > Sampler > Java Request

  • Classname은 이번에 만든 DynamicLengthTCP 선택합니다.
  • HostIP: 접속할 서버 IP 127.0.0.1 입력합니다.
  • Port: 접속할 서버 Port 1234 입력합니다.
  • Connect Timeout: 접속할때 처리할 timeout 시간은 대략 200 입력합니다.
  • Response Timeout: 응답 패킷을 받을때 timeout 시간은 대략 200 입력합니다.
  • Re-use connect: 하나의 thread에서 connect socket을 재사용할지 여부인데, True를 입력합니다.
  • Set NoDelay, SO_LINGER는 socket 옵션 참고합니다. (모르겠으면 false, 0)
  • Size of response header: 응답 패킷의 해더 사이즈를 입력합니다 (여기서는 8)
  • Offset of response body length: 응답 해더에서 body 사이즈가 포함된 위치를 입력합니다 (여기서는 6)
  • Size of response body length: body 사이즈가 저장된 크기를 입력합니다 (여기서는 2)
    • DynamicLengthTCP 에서는 사이즈가 1, 2, 4 일때만 처리 가능합니다.

7. "Java Request" 우클릭 > Add > Pre Processors > BeanShell PreProcessor

  • 요청 패킷을 만들기 위해 BeanShell을 작성합니다.
import java.io.*;
import org.apache.jmeter.protocol.tcp.sampler.*;

try {
  // msg from CSV Data Set Config
  String str = vars.get("msg");
  byte[] msg = str.getBytes();

  byte[] pkt = new byte[8 + msg.length];
  
  // version
  pkt[0] = (byte)0x01;
  
  // seq from Counter
  short seq = Short.parseShort(vars.get("reqCount"));
  pkt[1] = (byte)(seq>>>8);
  pkt[2] = (byte)seq;
  
  // constString
  pkt[3] = (byte)0x00;
  
  // type
  pkt[4] = (byte)0x00;
  pkt[5] = (byte)0x01;
  
  // len
  pkt[6] = (byte)(msg.length>>>8);
  pkt[7] = (byte)msg.length;
  
  // msg
  System.arraycopy(msg, 0, pkt, 8, msg.length);
  
  // byte array to hex string
  char[] hexArray = "0123456789ABCDEF".toCharArray();
  char[] hexChars = new char[pkt.length *2];
  for(int i=0; i<pkt.length; i++) {
  	int v = pkt[i] & 0xff;
  	hexChars[i * 2] = hexArray[v>>>4];
  	hexChars[i * 2 + 1] = hexArray[v & 0x0f];
  }
  
  str = new String(hexChars);
  vars.put("RequestData", str);
  
  log.info("Request data size: " + str.length());
}catch(Exception e) {
  log.info("Detect BeamShell PreProcessor Exception"+ e);
  prev.setTopThread(true);
}

8. "Java Request" 우클릭 > Add > Pre Processors > BeanShell PreProcessor

  • 응답패킷 파싱을 위해 BeanShell을 작성합니다.
import java.nio.ByteBuffer;
import org.apache.jmeter.samplers.SampleResult;

byte[] resData = prev.getResponseData();
int resSize = resData.length;

byte[] buf1 = {resData[4], resData[5]};
byte[] buf2 = {resData[6], resData[7]};

short type = ByteBuffer.wrap(buf1).getShort();
short bodySize = ByteBuffer.wrap(buf2).getShort();

log.info("Rsponse Size: " + resSize +  ", type: " + type+ ", bodySize: " + bodySize);

9. Run > Start

  • 테스트 및 결과 확인합니다.


Windows 에서 포트 포워딩 기능 사용하기.

VMWare등을 사용하는데, Bridged 모드로 설정하지 않고 (IP 여유가 없거나, 설정하기 귀찮을때..)

기본값인 NAT 모드로 설정해두고 아래 명령으로 하시면 됩니다.


CMD 창을 관리자 모드로 열고,


* Forwarding 설정


> netsh interface portproxy add v4tov4 listenport=80 listenaddress=192.168.0.100 connectport=80 connectaddress=192.168.229.100

 


* Forwarding 해제


> netsh interface portproxy delete v4tov4 listenport=80 listenaddress=192.168.0.100

 


* 설명

    - listenport: 내 PC에서 Listen할 Port

    - listenaddress: 내 PC에서 Listen할 IP

    - connectport: VM에 포워딩할 포트

    - connectaddress: VM의 IP

리눅스 배포판별 docker 설치 방법

Docker 설치

공식 문서: http://docs.docker.com/installation/

  • 64 bit 기준으로 테스트 해봤습니다.


Red Hat Enterprise 6.2

공식 문서에는 rhel 7 부터 지원하지만, 아래 방법으로 설치 가능

  • yum 저장소를 centos 로 수정.
rpm -ivh  http://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm

vi /etc/yum.repos.d/epel.repo
# cd /etc/yum.repos.d
# cp rhel-debuginfo.repo rhel-debuginfo.repo.bak
# vim rhel-debuginfo.repo

baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch
#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=$basearch
  • update
yum repolist
yum update
  • Disable selinux & reboot
vi /etc/selinux/config
SELINUX=disabled

reboot now
  • 인증키 생성
rpm --import http://mirror.centos.org/centos/6/os/x86_64/RPM-GPG-KEY-CentOS-6
  • update
yum install libdevmapper.so.1.02
  • docker 설치
yum -y install docker-io
reboot now


Cent OS 6.6

yum install http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
yum install docker-io


Ubuntu 14.04

apt-get update
apt-get upgrade
apt-get install -y less vim openssh-server curl

apt-get install docker.io
    or
curl -sSL https://get.docker.com/ | sh


Debian 8

공식 문서대로는 설치가 안됨.

apt-get update
apt-get upgrade
apt-get install -y less vim openssh-server curl
wget http://ftp.kr.debian.org/debian/pool/main/a/apparmor/libapparmor1_2.9.0-3_amd64.deb
dpkg -i libapparmor1_2.9.0-3_amd64.deb
wget http://ftp.kr.debian.org/debian/pool/main/a/apt/libapt-pkg4.12_1.0.9.8.1_amd64.deb
dpkg -i libapt-pkg4.12_1.0.9.8.1_amd64.deb
wget http://ftp.kr.debian.org/debian/pool/main/a/apt/apt-transport-https_1.0.9.8.1_amd64.deb
dpkg -i apt-transport-https_1.0.9.8.1_amd64.deb
curl -sSL https://get.docker.com/ | sh



최적의 makrdown editor는 sublime text

OSX에서 다시 Windows환경으로 넘어오면서, makrdown editor를 찾아 해맸다.

결론은 OS 구분없이 Sublime Text 였다.

[Sublime Text 3] + [MarkdownEditing] 플러그인 + [OmniMarkdownPreviewer] 플러그인 + [Table Editor] 플러그인 조합이다.

문제는 Windows 환경에선 한글 입력창이 이질감이 느껴진다는 것인데,

그것도 [IMESupport] 플러그인으로 완벽하진 않지만 쓸만해 졌다.

하지만, OS 차이로 일부 단축키가 바뀐건 아직 적응이 잘 안된다.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
2017-04-04

[Visual Studio Code] + [Preview] 플러그인 조합을 사용해보고 있는데, 이것도 좋다.


문자열 치환

  • 하나의 파일에서 치환

    • vi 사용:%s/old/new/g
    • sed 사용sed -i 's/old/new/g' ./file.html
  • 여러 파일을 한꺼번에 치환find ./ -name "*.html" -exec sed -i 's/old/new/g' {} \;
  • 특정 파일만 제외하고 치환find . ! \( -name '*.jar' -prune \) -exec sed -i 's/old/new/g' {} \;


shell script로 구현하는 URL 상태 체크

Shell Script로 특정 URL의 상태를 체크하고자 합니다.


curl을 이용하면 간단 하네요.

curl은 결과를 exit code로 리턴 합니다.

exit codes list는 아래 man page를 참고하세요.

http://curl.haxx.se/docs/manpage.html )


#! /usr/bin/sh

function check {
     if [ $? -ne 0 ] ; then
         echo "Error occurred getting URL $1:"
         if [ $? -eq 6 ]; then
             echo "Unable to resolve host"
         fi
         if [$? -eq 7 ]; then
             echo "Unable to connect to host"
         fi
         exit 1
     fi

}
curl -s -o "/dev/null" $1
check;


[ 출처 ]

http://answers.google.com/answers/threadview/id/276934.html

shell script로 구현하는 multi tail


multi tail 유틸은 이미 있습니다.


http://www.vanheusden.com/multitail/


하지만, 설치를 해야 하죠


간단하게 shell script로 구현 하는 방법이 있어서 소개 합니다.



$ vi multitail.sh

#!/bin/ksh

function sig_int
{
   echo 'sig_int'
   kill `jobs -p`
}

for file in "$@"
do
  tail -f $file &
done
trap 'sig_int' 2

wait


이상입니다.

솔라리스 메일 전체 삭제

솔라리스에서 메일 전체 삭제 벙법.

콘솔에서 아래와 같이 입력하면 된다.


$ printf "d*q"|mailx -N


[출처] 

http://www.unix.com/unix-dummies-questions-answers/38598-delete-all-mails-solaris.html

CVS Commit 로그를 남겨보자

-. 요구사항 파악 및 설계

적용 패키지 작성을 위해 수정된 파일 목록을 추출 할 방법을 찾아 보기로 했다.
업무 때문에 소스 관리를 CVS 로 하고 있다. CVS의 단점은 개별 파일이력은 확인 할 수 있는데, 
수정된 파일 목록을 뽑기가 쉽지 않다는 것이다.

구글링을 해보니 CVS에서 Commit 시점에 뭔가 처리할 수 있다는 것을 알았다.

먼저, CVS 서버에 접속해서 저장소의 CVSROOT 디렉토리를 보자

-rwxrwxr-- 1 cvs cvs       544  4월 22 09:23 checkoutlist

-rwxrwxr-- 1 cvs cvs       694  3월 21  2009 checkoutlist,v

-rwxrwxr-- 1 cvs cvs       882  4월 26 16:03 commitinfo

-rwxrwxr-- 1 cvs cvs       959  3월 21  2009 commitinfo,v

-rwxrwxr-- 1 cvs cvs       993  3월 21  2009 config

-rwxrwxr-- 1 cvs cvs      1192  3월 21  2009 config,v

-rwxrwxr-- 1 cvs cvs       602  2월  8 16:32 cvswrappers

-rwxrwxr-- 1 cvs cvs       801  3월 21  2009 cvswrappers,v

-rwxrwxr-- 1 cvs cvs      1025  3월 21  2009 editinfo

-rwxrwxr-- 1 cvs cvs      1224  3월 21  2009 editinfo,v

-rwxrwxr-- 1 cvs cvs 292081967  4월 29 18:14 history

-rwxrwxr-- 1 cvs cvs      1245  4월 21 11:32 loginfo

-rwxrwxr-- 1 cvs cvs      1367  3월 21  2009 loginfo,v

-rwxrwxr-- 1 cvs cvs      1151  3월 21  2009 modules

-rwxrwxr-- 1 cvs cvs      1350  3월 21  2009 modules,v

-rwxrwxr-- 1 cvs cvs       564  3월 21  2009 notify

-rwxrwxr-- 1 cvs cvs       763  3월 21  2009 notify,v

-rwxrwxr-- 1 cvs cvs       640  4월 25 12:43 passwd

-rwxrwxr-- 1 cvs cvs       649  3월 21  2009 rcsinfo

-rwxrwxr-- 1 cvs cvs       848  3월 21  2009 rcsinfo,v

-rwxrwxr-- 1 cvs cvs       879  3월 21  2009 taginfo

-rwxrwxr-- 1 cvs cvs      1078  3월 21  2009 taginfo,v

-rwxrwxrw- 1 cvs cvs       187 11월 10 17:15 val-tags

-rwxrwxr-- 1 cvs cvs      1026  3월 21  2009 verifymsg

-rwxrwxr-- 1 cvs cvs      1225  3월 21  2009 verifymsg,v


위와 같은 파일들을 볼 수 있는데.. 
여기서 commitinfo 란 파일이 CVS에서 Commit 하기 바로전에 실행되는 파일이다.

방향은 정해졌다. 
작업 구성은 아래와 같은 순서로 하기로 했다.

     1. CVS가 Commit 되는 시점에 파일명을 DB에 저장한다.
         -> CVSROOT/commitinfo에 등록할 쉘 스크립트를 작성.
     2.  저장된 DB를 조회할 간단한 WEB Page 제작
         -> 평소 관심을 갖고 있던, jQuery를 이용.

[ 1. CVS가 Commit 되는 시점에 파일명을 DB에 저장한다 ]

commitinfo 파일에 아래와 같이 내용을 추가 한다.

$ vi CVSROOT/commitinfo 

ALL /home/cvs/myproject/CVSROOT/cvscommitlog.sh 


DB에 테이블을 생성 한다.

CREATE TABLE CVS_COMMIT
(
   SEQ              NUMBER(10) NOT NULL,
   USERID           VARCHAR2(20) NOT NULL,
   REG_DATE         DATE DEFAULT SYSDATE,
   PROJECT          VARCHAR2(50) NOT NULL,
   FILENAME         VARCHAR2(500) NOT NULL
);

* index는 REG_DATE, TO_CHAR(reg_date, 'YYYY/MM/DDHH24:MI'), USERID, PROJECT 를 걸었다.

commitinfo 파일이 실행할 쉘스크립트를 작성한다.

#!/bin/sh
export ORACLE_BASE=/oracle
export ORACLE_HOME=$ORACLE_BASE/product/10.2.0/db_1
export ORACLE_SID=ORACLE_SID
export PATH=$PATH:$ORACLE_HOME/bin
export ORACLE_OWNER=oracle
export LANG=c
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib;

DATE=`date "+%Y-%m-%d %H:%M:%S"`
DT=`date "+%Y%m%d"`
LOG_FILE="/home/cvs/cvs_log/commit.$DT.log"
DIR=$1
USER=$USER
PROJECT=`echo $DIR | cut -d/ -f4`
#(echo ""; id;) >> $LOG_FILE
shift
for file in $*
do
    echo "$PROJECT, $USER, $DATE, $DIR/$file" >> $LOG_FILE
        result=`$ORACLE_HOME/bin/sqlplus oracle_user/oracle_pwd@oracledb <<EOF 
        INSERT INTO CVS_COMMIT 
        VALUES ( (SELECT NVL(MAX(SEQ), 0) +1 FROM CVS_COMMIT), 
                       '$USER', TO_DATE('$DATE', 'YYYY-MM-DD hh24:mi:ss'), 
                       '$PROJECT', '$DIR/$file');
        COMMIT;
        EOF
        `
done



[ 2.  저장된 DB를 조회할 간단한 WEB Page 제작 ]
jsp 파일 하나와 html파일 하나로 이루어졌고, jQuery와 JSON을 이용 했다.
DATE Picker는 jQuery Plugin 중에 하나를 이용했다.