#!/bin/bash # ================================================================================ # # LOCALDUMP :: COUCHDB2 - using nodejs tools couchbackup and couchrestore # https://github.com/cloudant/couchbackup # # creates gzipped plain text backups (JSON) from each scheme # # -------------------------------------------------------------------------------- # ah - Axel Hahn <axel.hahn@iml.unibe.ch> # ds - Daniel Schueler <daniel.schueler@iml.unibe.ch> # # 2019-11-13 ..... v1.0 initial version with backup and restore (single DB) # 2020-05-19 ..... v1.1 backup a single or multiple couchdb instances by globbing param # ./localdump.sh backup couchdb2 demo # 2021-10-11 ..... v1.2 added fastmode in restore: no test connect, do not # delete DB before create request # ================================================================================ if [ -z $BACKUP_TARGETDIR ]; then echo ERROR: you cannot start `basename $0` directly rc=$rc+1 exit 1 fi # -------------------------------------------------------------------------------- # CONFIG # -------------------------------------------------------------------------------- # contains *.config files for each instance CFGDIR=~/.iml_backup/couchdb2 # UNUSED # dirPythonPackages=/usr/lib/python2.7/site-packages ARCHIVE_DIR=`_j_getvar ${JOBFILE} dir-dbarchive`/couchdb2 # -------------------------------------------------------------------------------- # FUNCTIONS # -------------------------------------------------------------------------------- # make an couch api request # param string method ... one of GET|POST|DELETE # param string relative url, i.e. _all_dbs or _stats function _couchapi(){ method=$1 apiurl=$2 outfile=$3 sParams= # sParams="$sParams -u ${couchdbuser}:${couchdbpw}" sParams="$sParams -X ${method}" sParams="$sParams ${COUCH_URL}${apiurl}" if [ ! -z $outfile ]; then sParams="$sParams -o ${outfile}" fi curl $sParams 2>/dev/null } function _getDblist(){ _couchapi GET _all_dbs | sed 's#\"#\n#g' | egrep -v "^(\[|\,|\])$" | grep -v _replicator | grep -v _global_changes } # get value update_seq of given couchdb name function _getDbSeq(){ # _couchapi GET $1 | sed 's#,\"#\n"#g' | egrep -v "^(\[|\,|\])$" | grep update_seq | cut -f 4 -d '"' _couchapi GET $1 | sed 's#,\"#\n"#g' | egrep -v "^(\[|\,|\])$" | grep update_seq | cut -f 4 -d '"' | cut -f 1 -d '-' } # ---------- CONFIG/ INSTANCES # get valid configured instances function getInstances(){ for mycfg in `ls -1 ${CFGDIR}/*${1}*.config` do . $mycfg if [ $? -eq 0 ]; then echo `basename "${mycfg}" | cut -f 1 -d "."` fi done } # load the config of an existing instance # see getInstances to get valid names # param string name of the instance to load function loadInstance(){ COUCH_URL= . ${CFGDIR}/${1}.config if [ $? -ne 0 ]; then color error echo ERROR: invalid instance: $1 - the config file cannot be sourced color reset exit 1 fi if [ -z ${COUCH_URL} ]; then color error echo ERROR: invalid instance: $1 - the config file has no COUCH_URL color reset exit 1 fi } # ---------- BACKUP # backup with loop over instances # param 1 string globbing filter to config files function doBackup(){ # for mycfg in `ls -1 ~/.iml_backup/couchdb/*.config` for COUCHDB_INSTANCE in `getInstances $1` do loadInstance $COUCHDB_INSTANCE echo --- instance: $COUCHDB_INSTANCE curl --head -X GET $COUCH_URL 2>/dev/null | grep "^HTTP.*\ 200\ " if [ $? -eq 0 ]; then echo OK, connected. sleep 2 _doBackupOfSingleInstance else rc=$rc+1 color error echo ERROR: couch DB instance is not available or canot be accessed with these credentials in config file # repeat curl to show the error message curl -X GET $COUCH_URL color reset fi echo echo --- `date` done. echo done } # make backup of all databases in a couchdb instance # global: COUCH_URL # global: COUCHDB_INSTANCE function _doBackupOfSingleInstance(){ create_targetdir mkdir -p ${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE} 2>/dev/null mkdir -p ${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/seq 2>/dev/null typeset -i iDbTotal=`_getDblist | wc -l` typeset -i iDb=0 echo echo " DUMP databases of instance ${COUCHDB_INSTANCE}: $iDbTotal databases" echo " TO BACKUP ${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE}" echo " ARCHIVE ${ARCHIVE_DIR}/${COUCHDB_INSTANCE}" echo for dbname in `_getDblist` do iDb=$iDb+1 echo -n "----- `date` ${COUCHDB_INSTANCE} -- $iDb of $iDbTotal - ${dbname} - " OUTFILE=${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE}/`get_outfile ${dbname}`.couchdbdump ARCHIVFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/${dbname}.couchdbdump.gz SEQFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/seq/__seq__${dbname} sSequenceCurrent=`_getDbSeq ${dbname}` sSequenceLast=`cat ${SEQFILE} 2>/dev/null | cut -f 1 -d '-'` # sSequenceLast=`cat ${SEQFILE} 2>/dev/null | tr -d '\n'` # echo # echo "update_seq --+-- current [${sSequenceCurrent}]" # echo " +-- backup [${sSequenceLast}]" if [ ! -z ${sSequenceLast} ] || [ "${sSequenceCurrent}" = "${sSequenceLast}" ]; then echo SKIP: still on sequence ${sSequenceLast} else echo echo "update_seq --+-- current [${sSequenceCurrent}]" echo " +-- backup [${sSequenceLast}]" echo -n "Need to backup ... " couchbackup --db ${dbname} >${OUTFILE}.progress 2>/dev/null && mv ${OUTFILE}.progress ${OUTFILE} fetchrc # $myrc is last returncode - set in fetchrc if [ $myrc -eq 0 ]; then echo -n "gzip ... " compress_file $OUTFILE fetchrc if [ $myrc -eq 0 ]; then cp ${OUTFILE}* ${ARCHIVFILE} && echo ${sSequenceCurrent}>${SEQFILE} ls -l ${ARCHIVFILE} ${SEQFILE} fi else echo "ERROR occured while dumping - abort" fi ls -l $OUTFILE* echo fi done } # ---------- RESTORE # restore a single backup file; the instance and db name will be detected from file # param string filename of db dump (full path or relative to BACKUP_TARGETDIR) function restoreByFile(){ sMyfile=$1 sMyDb=$2 bFastMode=1 echo h2 "analyze dump $sMyfile" COUCHDB_INSTANCE=`echo $sMyfile | sed "s#${BACKUP_TARGETDIR}##g" | sed "s#\./##g" | sed "s#^/##g" | cut -f 1 -d "/"` echo "detected COUCHDB_INSTANCE : [${COUCHDB_INSTANCE}]" if [ -z $sMyDb ]; then sMyDb=`guessDB $sMyfile` echo "detected db schema from file: [${sMyDb}]" else echo "db schema from param 2: [${sMyDb}]" fi echo loadInstance $COUCHDB_INSTANCE if [ $bFastMode -eq 0 ]; then echo connect $couchdbhost on port $couchdbport with user $couchdbuser curl --head -X GET $COUCH_URL 2>/dev/null | grep "^HTTP.*\ 200\ " >/dev/null if [ $? -ne 0 ]; then color error echo ERROR: couch DB instance is not available curl -X GET $COUCH_URL color reset exit 1 fi color ok echo OK color reset fi echo # _getDblist | grep "^${sMyDb}$" # if [ $? -eq 0 ]; then # echo DB exists ... need to drop it first # fi if [ $bFastMode -eq 0 ]; then h2 deleting database [$sMyDb] ... color cmd _couchapi DELETE $sMyDb fetchrc color reset fi h2 creating database [$sMyDb] ... color cmd _couchapi PUT $sMyDb fetchrc color reset h2 import file ... color cmd # zcat ${sMyfile} | python ${dirPythonPackages}/couchdb/tools/load.py $COUCH_URL/$sMyDb zcat ${sMyfile} | couchrestore --db $sMyDb fetchrc color reset echo } # -------------------------------------------------------------------------------- # MAIN # -------------------------------------------------------------------------------- # ----- check requirements # --- is a couchd here # j_requireProcess "couchdb" 1 # --- very specific :-/ ... check available config files ls -1 ${CFGDIR}/* >/dev/null 2>&1 rc=$rc+$? if [ $rc -eq 0 ]; then echo OK: couchdb2 config was found on this system ... checking requirements for backup ... j_requireBinary "curl" 1 j_requireBinary "couchbackup" 1 j_requireBinary "couchrestore" 1 #ls ${dirPythonPackages}/couchdb/tools/dump.py ${dirPythonPackages}/couchdb/tools/load.py >/dev/null && echo "OK: python couchdb tools were found" #rc=$rc+$? if [ $rc -eq 0 ]; then echo if [ "$1" = "restore" ]; then echo shift 1 restoreByFile $* else shift 1 # remove keyword ALL which is used for localdump.sh to loop over all db types test "$1" = "ALL" && shift 1 doBackup $* fi else color error echo ERROR: Couchdb is here but I am missing things for the backup :-/ color reset fi else rc=0 echo "SKIP: couchdb2 config does not seem to be here" fi echo $0 $* [couchdb2] final returncode rc=$rc # --------------------------------------------------------------------------------