Compare commits

..

9 Commits

Author SHA1 Message Date
aa8a68aa80 EOF & whitespace
Some checks failed
lint / docker (push) Failing after 4m47s
2024-11-29 00:54:31 +01:00
7fcc5afc64 EOF & whitespace 2024-11-29 00:54:09 +01:00
e7e98d0b47 few fixes and lint compatible 2024-11-29 00:48:59 +01:00
8479c378ee fix basic 2024-11-29 00:32:39 +01:00
274e1e2e59 requirements.txt 2024-11-29 00:02:24 +01:00
eb0bdaedbd fix import 2024-11-28 23:59:02 +01:00
99dc6e0abf fix import 2024-11-28 23:46:48 +01:00
e8ba6df102 fix first pass - .gitignore 2024-11-28 23:21:26 +01:00
ffd9bf3d39 fix first pass 2024-11-28 23:20:19 +01:00
81 changed files with 4779 additions and 4628 deletions

View File

@ -17,9 +17,8 @@ jobs:
python-version: '3.12'
cache: 'pip' # caching pip dependencies
- run: pip install ruff
- run: |
- run: |
ruff check .
ruff fix .
# - uses: stefanzweifel/git-auto-commit-action@v4
# with:
# commit_message: 'style fixes by ruff'

2
.gitignore vendored
View File

@ -2,11 +2,13 @@
*.swp
*~
*.pyc
__pycache__/*
/tasks.sqlite
/tasks.sqlite-wal
/srvinstallation
/tasks.sqlite-shm
.idea
.ruff_cache/*
/deb/builddir
/deb/*.deb
/lib

13
.hadolint.yml Normal file
View File

@ -0,0 +1,13 @@
DL3008failure-threshold: warning
format: tty
ignored:
- DL3007
override:
error:
- DL3015
warning:
- DL3015
info:
- DL3008
style:
- DL3015

7
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,7 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml

2
.vscode/launch.json vendored
View File

@ -19,4 +19,4 @@
"console": "integratedTerminal"
}
]
}
}

20
Dockerfile Executable file
View File

@ -0,0 +1,20 @@
FROM python:3.12-slim
WORKDIR /opt/tisbackup
COPY entrypoint.sh /entrypoint.sh
COPY . /opt/tisbackup
RUN apt-get update \
&& apt-get install --no-install-recommends -y rsync ssh cron \
&& rm -rf /var/lib/apt/lists/* \
&& /usr/local/bin/python3.12 -m pip install --no-cache-dir -r requirements.txt \
&& mkdir -p /var/spool/cron/crontabs \
&& echo '59 03 * * * root /bin/bash /opt/tisbackup/backup.sh' > /etc/crontab \
&& echo '' >> /etc/crontab \
&& crontab /etc/crontab
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/usr/local/bin/python3.12","/opt/tisbackup/tisbackup_gui.py"]

41
compose.yml Executable file
View File

@ -0,0 +1,41 @@
services:
tisbackup_gui:
container_name: tisbackup_gui
image: "tisbackup:latest"
build: .
volumes:
- ./config/:/etc/tis/
- ./backup/:/backup/
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
ports:
- 9980:8080
deploy:
resources:
limits:
cpus: 0.50
memory: 512M
reservations:
cpus: 0.25
memory: 128M
tisbackup_cron:
container_name: tisbackup_cron
image: "tisbackup:latest"
build: .
volumes:
- ./config/:/etc/tis/
- ./ssh/:/config_ssh/
- ./backup/:/backup/
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
restart: always
command: "/bin/bash /opt/tisbackup/cron.sh"
deploy:
resources:
limits:
cpus: 0.50
memory: 512M
reservations:
cpus: 0.25
memory: 128M

13
config.py Normal file → Executable file
View File

@ -1,10 +1,9 @@
import os,sys
from huey.backends.sqlite_backend import SqliteQueue,SqliteDataStore
from huey.api import Huey, create_task
import os
import sys
from huey.contrib.sql_huey import SqlHuey
from huey.storage import SqliteStorage
tisbackup_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__)))
tasks_db = os.path.join(tisbackup_root_dir,"tasks.sqlite")
queue = SqliteQueue('tisbackups',tasks_db)
result_store = SqliteDataStore('tisbackups',tasks_db)
huey = Huey(queue,result_store,always_eager=False)
tasks_db = os.path.join(tisbackup_root_dir, "tasks.sqlite")
huey = SqlHuey(name="tisbackups",filename=tasks_db,always_eager=False,storage_class=SqliteStorage)

4
cron.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
set -x
echo "Starting cron job for TIS Backup"
cron -f -l 2

View File

@ -1,42 +1,42 @@
## tisbackup for python3
## tisbackup for python3
### Install
Once the deb package is created, one can use it to install tisbackup on a debian machine. The command is:
Once the deb package is created, one can use it to install tisbackup on a debian machine. The command is:
```
apt install ./tis-tisbackup-1-2-0.170-deb11.deb
```
```
Note that the version numbers might be different depending on the system you used to build the package.
Then create a directory where to backup the files from your machines. The default is ```/backup```.
This can be changed in the configuration file ```/etc/tis/tisback-config.ini```. Usually this
directory is mounted from a shared ressource on a NAS with great capacity.
directory is mounted from a shared ressource on a NAS with great capacity.
Configure your backup jobs:
Configure your backup jobs:
```
cd /etc/tis
cp tisbackup-config.ini.sample tisbackup-config.ini
vi tisbackup-config.ini
```
```
After this, one have to generate the public and private certificates, as root:
After this, one have to generate the public and private certificates, as root:
```
cd
ssh-keygen -t rsa -b 2048
```
```
(press enter for each step)
Then propagate the public certificate on the machines targetted for backup:
Then propagate the public certificate on the machines targetted for backup:
```
ssh-copy-id -i /root/.ssh/id_rsa.pub root@machine1
ssh-copy-id -i /root/.ssh/id_rsa.pub root@machine2
```
```
etc.
Eventually modify ```/etc/cron.d/tisbackup``` for your needs.
Finalize the installation with:
Finalize the installation with:
```
tisbackup -d backup
systemctl start tisbackup_gui
@ -52,5 +52,3 @@ The documentation for tisbackup is here: [tisbackup doc](https://tisbackup.readt
dpkg --force-all --purge tis-tisbackup
apt autoremove
```

View File

@ -3,8 +3,7 @@ Version: 1-__VERSION__
Section: base
Priority: optional
Architecture: all
Depends: unzip, ssh, rsync, python3-paramiko, python3-pyvmomi, python3-pexpect, python3-flask,python3-simplejson, python3-pip
Depends: unzip, ssh, rsync, python3-paramiko, python3-pyvmomi, python3-pexpect, python3-flask,python3-simplejson, python3-pip
Maintainer: Tranquil-IT <technique@tranquil.it>
Description: TISBackup backup management
Description: TISBackup backup management
Homepage: https://www.tranquil.it

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
VERSION_DEB=$(cat /etc/debian_version | cut -d "." -f 1)
VERSION_DEB=$(cat /etc/debian_version | cut -d "." -f 1)
VERSION_SHORT=$(cat ../tisbackup.py | grep "__version__" | cut -d "=" -f 2 | sed 's/"//g')
GIT_COUNT=`git rev-list HEAD --count`
GIT_COUNT=`git rev-list HEAD --count`
VERSION="${VERSION_SHORT}.${GIT_COUNT}-deb${VERSION_DEB}"
rm -f *.deb
@ -32,5 +32,3 @@ rsync -aP ../samples/tisbackup-config.ini.sample ./builddir/etc/tis/tisbackup-c
chmod 755 ./builddir/opt/tisbackup/tisbackup.py
dpkg-deb --build builddir tis-tisbackup-1-${VERSION}.deb

View File

@ -765,4 +765,4 @@ div.math:hover a.headerlink {
#top-link {
display: none;
}
}
}

View File

@ -1 +1 @@
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 433 KiB

File diff suppressed because one or more lines are too long

View File

@ -9,4 +9,4 @@ var DOCUMENTATION_OPTIONS = {
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false
};
};

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 433 KiB

View File

@ -1 +1 @@
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}});
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}});

View File

@ -1,4 +1,4 @@
/**
* @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document);
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document);

View File

@ -1,4 +1,4 @@
/**
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document);
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document);

View File

@ -1 +1 @@
!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("<div class='wy-table-responsive'></div>"),n("table.docutils.footnote").wrap("<div class='wy-table-responsive footnote'></div>"),n("table.docutils.citation").wrap("<div class='wy-table-responsive citation'></div>"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n('<span class="toctree-expand"></span>'),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}t.length>0&&($(".wy-menu-vertical .current").removeClass("current"),t.addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l1").parent().addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current"),t.closest("li.toctree-l5").addClass("current"),t[0].scrollIntoView())}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t<e.length&&!window.requestAnimationFrame;++t)window.requestAnimationFrame=window[e[t]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[t]+"CancelAnimationFrame"]||window[e[t]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(e,t){var i=(new Date).getTime(),o=Math.max(0,16-(i-n)),r=window.setTimeout((function(){e(i+o)}),o);return n=i+o,r}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(n){clearTimeout(n)})}()}).call(window)},function(n,e){n.exports=jQuery},function(n,e,t){}]);
!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("<div class='wy-table-responsive'></div>"),n("table.docutils.footnote").wrap("<div class='wy-table-responsive footnote'></div>"),n("table.docutils.citation").wrap("<div class='wy-table-responsive citation'></div>"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n('<span class="toctree-expand"></span>'),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}t.length>0&&($(".wy-menu-vertical .current").removeClass("current"),t.addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l1").parent().addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current"),t.closest("li.toctree-l5").addClass("current"),t[0].scrollIntoView())}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t<e.length&&!window.requestAnimationFrame;++t)window.requestAnimationFrame=window[e[t]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[t]+"CancelAnimationFrame"]||window[e[t]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(e,t){var i=(new Date).getTime(),o=Math.max(0,16-(i-n)),r=window.setTimeout((function(){e(i+o)}),o);return n=i+o,r}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(n){clearTimeout(n)})}()}).call(window)},function(n,e){n.exports=jQuery},function(n,e,t){}]);

View File

@ -13,7 +13,7 @@
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
/* Non-minified version JS is _stemmer.js if file is provided */
/* Non-minified version JS is _stemmer.js if file is provided */
/**
* Porter Stemmer
*/
@ -293,5 +293,3 @@ function splitQuery(query) {
}
return result;
}

View File

@ -71,4 +71,4 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left:
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */

View File

@ -9,75 +9,75 @@
<meta content="Documentation, TISBackup, configuration, backup jobs" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Configuring the backup jobs &mdash; TISBackup 1.8.2 documentation</title>
<title>Configuring the backup jobs &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Using TISBackup" href="using_tisbackup.html" />
<link rel="prev" title="Installing and configuring TISBackup on Debian" href="installing_tisbackup.html" />
<link rel="prev" title="Installing and configuring TISBackup on Debian" href="installing_tisbackup.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -86,17 +86,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -125,29 +125,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -168,28 +168,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Configuring the backup jobs</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/configuring_tisbackup.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="configuring-the-backup-jobs">
<h1>Configuring the backup jobs<a class="headerlink" href="#configuring-the-backup-jobs" title="Permalink to this headline"></a></h1>
<p id="configuring-backup-jobs">The configuration of the backups is done in an <em class="mimetype">.ini</em> file,
@ -440,7 +440,7 @@ with read-write access only for it.</p>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -456,14 +456,14 @@ with read-write access only for it.</p>
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -472,7 +472,7 @@ with read-write access only for it.</p>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -480,11 +480,11 @@ with read-write access only for it.</p>
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -499,4 +499,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -5,75 +5,75 @@
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &mdash; TISBackup 1.8.2 documentation</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="#" />
<link rel="search" title="Search" href="search.html" />
<link rel="search" title="Search" href="search.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -82,17 +82,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -106,29 +106,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -149,36 +149,36 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Index</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
</div>
</div>
</div>
<footer>
@ -190,14 +190,14 @@
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -206,7 +206,7 @@
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -214,11 +214,11 @@
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -233,4 +233,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -9,74 +9,74 @@
<meta content="Documentation, TISBackup, introduction, welcome page, Welcome" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Presenting TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<title>Presenting TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Technical background for TISBackup" href="presenting_tisbackup.html" />
<link rel="next" title="Technical background for TISBackup" href="presenting_tisbackup.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="#" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -85,17 +85,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -109,29 +109,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="#">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -152,28 +152,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="#" class="icon icon-home"></a> &raquo;</li>
<li>Presenting TISBackup</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/index.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<figure class="align-center">
<a class="reference internal image-reference" href="_images/tisbackup_logo.png"><img alt="TISBackup Logo" src="_images/tisbackup_logo.png" style="width: 700.0px; height: 206.0px;" /></a>
</figure>
@ -275,7 +275,7 @@ if there is a problem during the backup.</p></li>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -290,14 +290,14 @@ if there is a problem during the backup.</p></li>
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -306,7 +306,7 @@ if there is a problem during the backup.</p></li>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -314,11 +314,11 @@ if there is a problem during the backup.</p></li>
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -333,4 +333,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -9,75 +9,75 @@
<meta content="Documentation, TISBackup, installation, configuration" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Installing and configuring TISBackup on Debian &mdash; TISBackup 1.8.2 documentation</title>
<title>Installing and configuring TISBackup on Debian &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Configuring the backup jobs" href="configuring_tisbackup.html" />
<link rel="prev" title="Technical background for TISBackup" href="presenting_tisbackup.html" />
<link rel="prev" title="Technical background for TISBackup" href="presenting_tisbackup.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -86,17 +86,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -123,29 +123,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -166,28 +166,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Installing and configuring TISBackup on Debian</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/installing_tisbackup.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="installing-and-configuring-tisbackup-on-debian">
<h1>Installing and configuring TISBackup on Debian<a class="headerlink" href="#installing-and-configuring-tisbackup-on-debian" title="Permalink to this headline"></a></h1>
<section id="setting-up-the-gnu-linux-debian-server">
@ -423,7 +423,7 @@ of your TISBackup server on port 8080.</p>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -439,14 +439,14 @@ of your TISBackup server on port 8080.</p>
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -455,7 +455,7 @@ of your TISBackup server on port 8080.</p>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -463,11 +463,11 @@ of your TISBackup server on port 8080.</p>
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -482,4 +482,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -9,75 +9,75 @@
<meta content="Documentation, TISBackup, technical background" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Technical background for TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<title>Technical background for TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Installing and configuring TISBackup on Debian" href="installing_tisbackup.html" />
<link rel="prev" title="Presenting TISBackup" href="index.html" />
<link rel="prev" title="Presenting TISBackup" href="index.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -86,17 +86,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul class="current">
<li class="toctree-l1 current"><a class="current reference internal" href="#">Technical background for TISBackup</a><ul>
@ -116,29 +116,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -159,28 +159,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Technical background for TISBackup</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/presenting_tisbackup.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="technical-background-for-tisbackup">
<h1>Technical background for TISBackup<a class="headerlink" href="#technical-background-for-tisbackup" title="Permalink to this headline"></a></h1>
<p>The deduplication of this solution is based on the hardlinks
@ -274,7 +274,7 @@ and <a class="reference internal" href="installing_tisbackup.html#base-debian-se
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -290,14 +290,14 @@ and <a class="reference internal" href="installing_tisbackup.html#base-debian-se
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -306,7 +306,7 @@ and <a class="reference internal" href="installing_tisbackup.html#base-debian-se
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -314,11 +314,11 @@ and <a class="reference internal" href="installing_tisbackup.html#base-debian-se
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -333,4 +333,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -9,74 +9,74 @@
<meta content="Documentation, TISBackup, screenshots" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Screenshots of TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<title>Screenshots of TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="prev" title="Contacting Tranquil IT" href="tranquil-it-contacts.html" />
<link rel="prev" title="Contacting Tranquil IT" href="tranquil-it-contacts.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -85,17 +85,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -109,29 +109,29 @@
<li class="toctree-l1 current"><a class="current reference internal" href="#">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -152,28 +152,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Screenshots of TISBackup</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/screenshots.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="screenshots-of-tisbackup">
<h1>Screenshots of TISBackup<a class="headerlink" href="#screenshots-of-tisbackup" title="Permalink to this headline"></a></h1>
<figure class="align-center" id="id1">
@ -222,7 +222,7 @@
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -237,14 +237,14 @@
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -253,7 +253,7 @@
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -261,11 +261,11 @@
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -280,4 +280,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -4,78 +4,78 @@
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &mdash; TISBackup 1.8.2 documentation</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<script type="text/javascript" src="_static/searchtools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="#" />
<link rel="search" title="Search" href="#" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="#" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -84,17 +84,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -108,29 +108,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -151,24 +151,24 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Search</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
@ -177,13 +177,13 @@
</div>
</noscript>
<div id="search-results">
</div>
</div>
</div>
<footer>
@ -195,14 +195,14 @@
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -211,7 +211,7 @@
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -219,17 +219,17 @@
});
</script>
<script type="text/javascript">
jQuery(function() { Search.loadIndex("searchindex.js"); });
</script>
<script type="text/javascript" id="searchindexloader"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -245,4 +245,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

File diff suppressed because one or more lines are too long

View File

@ -9,75 +9,75 @@
<meta content="TISBackup, documentation, website, editor, Twitter, official website" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Contacting Tranquil IT &mdash; TISBackup 1.8.2 documentation</title>
<title>Contacting Tranquil IT &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Screenshots of TISBackup" href="screenshots.html" />
<link rel="prev" title="Using TISBackup" href="using_tisbackup.html" />
<link rel="prev" title="Using TISBackup" href="using_tisbackup.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -86,17 +86,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -110,29 +110,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -153,28 +153,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Contacting Tranquil IT</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/tranquil-it-contacts.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="contacting-tranquil-it">
<span id="contact-tranquil-it"></span><h1>Contacting Tranquil IT<a class="headerlink" href="#contacting-tranquil-it" title="Permalink to this headline"></a></h1>
<ul class="simple">
@ -185,7 +185,7 @@
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -201,14 +201,14 @@
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -217,7 +217,7 @@
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -225,11 +225,11 @@
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -244,4 +244,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

View File

@ -9,75 +9,75 @@
<meta content="Documentation, TISBackup, usage, options, exporting" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<title>Using TISBackup &mdash; TISBackup 1.8.2 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.css" type="text/css" />
<link rel="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Contacting Tranquil IT" href="tranquil-it-contacts.html" />
<link rel="prev" title="Configuring the backup jobs" href="configuring_tisbackup.html" />
<link rel="prev" title="Configuring the backup jobs" href="configuring_tisbackup.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup
</a>
<div class="version">
1.8
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
@ -86,17 +86,17 @@
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p><span class="caption-text">Presenting TISBackup</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li>
@ -113,29 +113,29 @@
<li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
@ -156,28 +156,28 @@
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Using TISBackup</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/using_tisbackup.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="using-tisbackup">
<h1>Using TISBackup<a class="headerlink" href="#using-tisbackup" title="Permalink to this headline"></a></h1>
<p id="id1">As seen in the <a class="reference internal" href="installing_tisbackup.html#install-tisbackup-debian"><span class="std std-ref">section on installing TISbackup</span></a>,
@ -291,7 +291,7 @@ e2label /dev/xvdc1 tisbackup
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -307,14 +307,14 @@ e2label /dev/xvdc1 tisbackup
</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
@ -323,7 +323,7 @@ e2label /dev/xvdc1 tisbackup
</section>
</div>
<script type="text/javascript">
jQuery(function () {
@ -331,11 +331,11 @@ e2label /dev/xvdc1 tisbackup
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -350,4 +350,4 @@ gtag('config', 'UA-89790248-2');
</body>
</html>
</html>

7
entrypoint.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
env >> /etc/environment
# execute CMD
echo "$@"
exec "$@"

View File

@ -55,11 +55,12 @@
# --------------------------------------------------------------------
import gettext
import six.moves.xmlrpc_client as xmlrpclib
import six.moves.http_client as httplib
import socket
import sys
import six.moves.http_client as httplib
import six.moves.xmlrpc_client as xmlrpclib
translation = gettext.translation('xen-xm', fallback = True)
API_VERSION_1_1 = '1.1'

View File

@ -15,4 +15,3 @@
# along with TISBackup. If not, see <http://www.gnu.org/licenses/>.
#
# -----------------------------------------------------------------------

View File

@ -21,6 +21,7 @@
import sys
try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko
@ -32,6 +33,7 @@ sys.stderr = sys.__stderr__
from libtisbackup.common import *
class backup_mysql(backup_generic):
"""Backup a mysql database as gzipped sql file through ssh"""
type = 'mysql+ssh'

View File

@ -18,16 +18,17 @@
#
# -----------------------------------------------------------------------
import os
import datetime
import os
from .common import *
class backup_null(backup_generic):
"""Null backup to register servers which don't need any backups
"""Null backup to register servers which don't need any backups
but we still want to know they are taken in account"""
type = 'null'
required_params = ['type','server_name','backup_name']
optional_params = []
@ -43,9 +44,8 @@ class backup_null(backup_generic):
return {}
def checknagios(self,maxage_hours=30):
return (nagiosStateOk,"No backups needs to be performed")
register_driver(backup_null)
if __name__=='__main__':
pass

View File

@ -18,6 +18,7 @@
#
# -----------------------------------------------------------------------
import sys
try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko
@ -27,15 +28,17 @@ except ImportError as e:
sys.stderr = sys.__stderr__
import datetime
import base64
import datetime
import os
from libtisbackup.common import *
import re
from libtisbackup.common import *
class backup_oracle(backup_generic):
"""Backup a oracle database as zipped file through ssh"""
type = 'oracle+ssh'
type = 'oracle+ssh'
required_params = backup_generic.required_params + ['db_name','private_key', 'userid']
optional_params = ['username', 'remote_backup_dir', 'ignore_error_oracle_code']
db_name=''
@ -44,7 +47,7 @@ class backup_oracle(backup_generic):
ignore_error_oracle_code = [ ]
def do_backup(self,stats):
self.logger.debug('[%s] Connecting to %s with user %s and key %s',self.backup_name,self.server_name,self.username,self.private_key)
try:
mykey = paramiko.RSAKey.from_private_key_file(self.private_key)
@ -54,8 +57,8 @@ class backup_oracle(backup_generic):
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(self.server_name,username=self.username,pkey = mykey,port=self.ssh_port)
self.ssh.connect(self.server_name,username=self.username,pkey = mykey,port=self.ssh_port)
t = datetime.datetime.now()
self.backup_start_date = t.strftime('%Y%m%d-%Hh%Mm%S')
dumpfile= self.remote_backup_dir + '/' + self.db_name + '_' + self.backup_start_date+'.dmp'
@ -68,10 +71,10 @@ class backup_oracle(backup_generic):
else:
print(('mkdir "%s"' % self.dest_dir))
else:
raise Exception('backup destination directory already exists : %s' % self.dest_dir)
raise Exception('backup destination directory already exists : %s' % self.dest_dir)
# dump db
stats['status']='Dumping'
cmd = "exp '%s' file='%s' grants=y log='%s'"% (self.userid,dumpfile, dumplog)
cmd = "exp '%s' file='%s' grants=y log='%s'"% (self.userid,dumpfile, dumplog)
self.logger.debug('[%s] Dump DB : %s',self.backup_name,cmd)
if not self.dry_run:
(error_code,output) = ssh_exec(cmd,ssh=self.ssh)
@ -94,7 +97,7 @@ class backup_oracle(backup_generic):
# zip the file
stats['status']='Zipping'
cmd = 'gzip %s' % dumpfile
cmd = 'gzip %s' % dumpfile
self.logger.debug('[%s] Compress backup : %s',self.backup_name,cmd)
if not self.dry_run:
(error_code,output) = ssh_exec(cmd,ssh=self.ssh)
@ -117,7 +120,7 @@ class backup_oracle(backup_generic):
stats['total_files_count']=1
stats['written_files_count']=1
stats['total_bytes']=os.stat(localpath).st_size
stats['written_bytes']=os.stat(localpath).st_size
stats['written_bytes']=os.stat(localpath).st_size
stats['log']='gzip dump of DB %s:%s (%d bytes) to %s' % (self.server_name,self.db_name, stats['written_bytes'], localpath)
stats['backup_location'] = self.dest_dir
stats['status']='RMTemp'
@ -131,7 +134,7 @@ class backup_oracle(backup_generic):
filelist = os.listdir(self.backup_dir)
filelist.sort()
p = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
p = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
for item in filelist:
if p.match(item):
dir_name = os.path.join(self.backup_dir,item)

View File

@ -18,6 +18,7 @@
#
# -----------------------------------------------------------------------
import sys
try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko
@ -29,6 +30,7 @@ sys.stderr = sys.__stderr__
from .common import *
class backup_pgsql(backup_generic):
"""Backup a postgresql database as gzipped sql file through ssh"""
type = 'pgsql+ssh'

View File

@ -18,19 +18,19 @@
#
# -----------------------------------------------------------------------
import os
import datetime
from libtisbackup.common import *
import time
import logging
import re
import os
import os.path
import datetime
import re
import time
from libtisbackup.common import *
class backup_rsync(backup_generic):
"""Backup a directory on remote server with rsync and rsync protocol (requires running remote rsync daemon)"""
type = 'rsync'
type = 'rsync'
required_params = backup_generic.required_params + ['remote_user','remote_dir','rsync_module','password_file']
optional_params = backup_generic.optional_params + ['compressionlevel','compression','bwlimit','exclude_list','protect_args','overload_args']
@ -46,7 +46,7 @@ class backup_rsync(backup_generic):
overload_args = None
compressionlevel = 0
def read_config(self,iniconf):
assert(isinstance(iniconf,ConfigParser))
@ -54,7 +54,7 @@ class backup_rsync(backup_generic):
if not self.bwlimit and iniconf.has_option('global','bw_limit'):
self.bwlimit = iniconf.getint('global','bw_limit')
if not self.compressionlevel and iniconf.has_option('global','compression_level'):
self.compressionlevel = iniconf.getint('global','compression_level')
self.compressionlevel = iniconf.getint('global','compression_level')
def do_backup(self,stats):
if not self.set_lock():
@ -216,7 +216,7 @@ class backup_rsync(backup_generic):
raise
finally:
finally:
self.remove_lock()
def get_latest_backup(self,current):
@ -225,8 +225,8 @@ class backup_rsync(backup_generic):
filelist.sort()
filelist.reverse()
full = ''
r_full = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
r_partial = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}.rsync$')
r_full = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
r_partial = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}.rsync$')
# we take all latest partials younger than the latest full and the latest full
for item in filelist:
if r_partial.match(item) and item<current:
@ -245,7 +245,7 @@ class backup_rsync(backup_generic):
filelist = os.listdir(self.backup_dir)
filelist.sort()
p = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
p = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
for item in filelist:
if p.match(item):
dir_name = os.path.join(self.backup_dir,item)
@ -288,7 +288,7 @@ class backup_rsync(backup_generic):
return False
else:
self.logger.info("[" + self.backup_name + "] incorrrect lock file : no pid line")
return False
return False
def set_lock(self):
@ -317,7 +317,7 @@ class backup_rsync(backup_generic):
class backup_rsync_ssh(backup_rsync):
"""Backup a directory on remote server with rsync and ssh protocol (requires rsync software on remote host)"""
type = 'rsync+ssh'
type = 'rsync+ssh'
required_params = backup_generic.required_params + ['remote_user','remote_dir','private_key']
optional_params = backup_generic.optional_params + ['compression','bwlimit','ssh_port','exclude_list','protect_args','overload_args', 'cipher_spec']
cipher_spec = ''
@ -341,4 +341,3 @@ if __name__=='__main__':
b.read_config(cp)
b.process_backup()
print((b.checknagios()))

View File

@ -18,20 +18,19 @@
#
# -----------------------------------------------------------------------
import os
import datetime
from .common import *
import time
import logging
import re
import os
import os.path
import datetime
import re
import time
from .common import *
class backup_rsync_btrfs(backup_generic):
"""Backup a directory on remote server with rsync and btrfs protocol (requires running remote rsync daemon)"""
type = 'rsync+btrfs'
type = 'rsync+btrfs'
required_params = backup_generic.required_params + ['remote_user','remote_dir','rsync_module','password_file']
optional_params = backup_generic.optional_params + ['compressionlevel','compression','bwlimit','exclude_list','protect_args','overload_args']
@ -47,7 +46,7 @@ class backup_rsync_btrfs(backup_generic):
overload_args = None
compressionlevel = 0
def read_config(self,iniconf):
assert(isinstance(iniconf,ConfigParser))
@ -55,7 +54,7 @@ class backup_rsync_btrfs(backup_generic):
if not self.bwlimit and iniconf.has_option('global','bw_limit'):
self.bwlimit = iniconf.getint('global','bw_limit')
if not self.compressionlevel and iniconf.has_option('global','compression_level'):
self.compressionlevel = iniconf.getint('global','compression_level')
self.compressionlevel = iniconf.getint('global','compression_level')
def do_backup(self,stats):
if not self.set_lock():
@ -74,7 +73,7 @@ class backup_rsync_btrfs(backup_generic):
returncode = process.returncode
if (returncode != 0):
self.logger.error("[" + self.backup_name + "] shell program exited with error code: %s"%log)
raise Exception("[" + self.backup_name + "] shell program exited with error code " + str(returncode), cmd)
raise Exception("[" + self.backup_name + "] shell program exited with error code " + str(returncode), cmd)
else:
self.logger.info("[" + self.backup_name + "] create btrs volume: %s"%dest_dir)
else:
@ -235,7 +234,7 @@ class backup_rsync_btrfs(backup_generic):
raise
finally:
finally:
self.remove_lock()
def get_latest_backup(self,current):
@ -244,8 +243,8 @@ class backup_rsync_btrfs(backup_generic):
filelist.sort()
filelist.reverse()
full = ''
r_full = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
r_partial = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}.rsync$')
r_full = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
r_partial = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}.rsync$')
# we take all latest partials younger than the latest full and the latest full
for item in filelist:
if r_partial.match(item) and item<current:
@ -263,7 +262,7 @@ class backup_rsync_btrfs(backup_generic):
filelist = os.listdir(self.backup_dir)
filelist.sort()
p = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
p = re.compile('^\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}$')
for item in filelist:
if p.match(item):
dir_name = os.path.join(self.backup_dir,item)
@ -306,7 +305,7 @@ class backup_rsync_btrfs(backup_generic):
return False
else:
self.logger.info("[" + self.backup_name + "] incorrrect lock file : no pid line")
return False
return False
def set_lock(self):
@ -335,7 +334,7 @@ class backup_rsync_btrfs(backup_generic):
class backup_rsync__btrfs_ssh(backup_rsync_btrfs):
"""Backup a directory on remote server with rsync,ssh and btrfs protocol (requires rsync software on remote host)"""
type = 'rsync+btrfs+ssh'
type = 'rsync+btrfs+ssh'
required_params = backup_generic.required_params + ['remote_user','remote_dir','private_key']
optional_params = backup_generic.optional_params + ['compression','bwlimit','ssh_port','exclude_list','protect_args','overload_args','cipher_spec']
cipher_spec = ''
@ -359,4 +358,3 @@ if __name__=='__main__':
b.read_config(cp)
b.process_backup()
print((b.checknagios()))

View File

@ -21,6 +21,7 @@
import sys
try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko
@ -32,6 +33,7 @@ sys.stderr = sys.__stderr__
from .common import *
class backup_samba4(backup_generic):
"""Backup a samba4 databases as gzipped tdbs file through ssh"""
type = 'samba4'
@ -163,4 +165,4 @@ class backup_samba4(backup_generic):
self.logger.info('Skipping %s, already registered',dir_name)
register_driver(backup_samba4)
register_driver(backup_samba4)

View File

@ -21,6 +21,7 @@
import sys
try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko
@ -30,11 +31,13 @@ except ImportError as e:
sys.stderr = sys.__stderr__
import datetime
import base64
import datetime
import os
from .common import *
class backup_sqlserver(backup_generic):
"""Backup a SQLSERVER database as gzipped sql file through ssh"""
type = 'sqlserver+ssh'

View File

@ -18,23 +18,26 @@
#
# -----------------------------------------------------------------------
import os
import datetime
from .common import *
from . import XenAPI
import time
import logging
import re
import os.path
import datetime
import select
import urllib.request, urllib.error, urllib.parse, urllib.request, urllib.parse, urllib.error
import base64
import datetime
import logging
import os
import os.path
import re
import select
import socket
import requests
import pexpect
import time
import urllib.error
import urllib.parse
import urllib.request
from stat import *
import pexpect
import requests
from . import XenAPI
from .common import *
class backup_switch(backup_generic):
"""Backup a startup-config on a switch"""
@ -149,7 +152,7 @@ class backup_switch(backup_generic):
else:
child.sendline(self.switch_user)
child.expect(".*#")
child.sendline( "terminal datadump")
child.expect("#")
child.sendline( "show startup-config")
@ -259,4 +262,3 @@ if __name__=='__main__':
cp.read('/opt/tisbackup/configtest.ini')
b = backup_xva()
b.read_config(cp)

View File

@ -18,25 +18,25 @@
#
# -----------------------------------------------------------------------
from .common import *
import pyVmomi
from pyVmomi import vim
from pyVmomi import vmodl
from pyVim.connect import SmartConnect, Disconnect
from datetime import datetime, date, timedelta
import atexit
import getpass
from datetime import date, datetime, timedelta
import pyVmomi
import requests
from pyVim.connect import Disconnect, SmartConnect
from pyVmomi import vim, vmodl
# Disable HTTPS verification warnings.
from requests.packages import urllib3
from .common import *
urllib3.disable_warnings()
import os
import time
import re
import tarfile
import re
import time
import xml.etree.ElementTree as ET
from stat import *
@ -59,15 +59,15 @@ class backup_vmdk(backup_generic):
cookie_text = " " + cookie_value + "; $" + cookie_path
# Make a cookie
cookie = dict()
cookie[cookie_name] = cookie_text
cookie[cookie_name] = cookie_text
return cookie
def download_file(self,url, local_filename, cookie, headers):
r = requests.get(url, stream=True, headers=headers,cookies=cookie,verify=False)
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024*1024*64):
if chunk:
for chunk in r.iter_content(chunk_size=1024*1024*64):
if chunk:
f.write(chunk)
f.flush()
return local_filename
@ -78,7 +78,7 @@ class backup_vmdk(backup_generic):
try:
infos = HttpNfcLease.info
device_urls = infos.deviceUrl
vmdks = []
vmdks = []
for device_url in device_urls:
deviceId = device_url.key
deviceUrlStr = device_url.url
@ -89,7 +89,7 @@ class backup_vmdk(backup_generic):
cookie = self.make_compatible_cookie(si._stub.cookie)
headers = {'Content-Type': 'application/octet-stream'}
self.logger.debug("[%s] exporting disk: %s" %(self.server_name,diskFileName))
self.download_file(diskUrlStr, diskFileName, cookie, headers)
vmdks.append({"filename":diskFileName,"id":deviceId})
finally:
@ -99,8 +99,8 @@ class backup_vmdk(backup_generic):
def create_ovf(self,vm,vmdks):
ovfDescParams = vim.OvfManager.CreateDescriptorParams()
ovf = si.content.ovfManager.CreateDescriptor(vm, ovfDescParams)
root = ET.fromstring(ovf.ovfDescriptor)
ovf = si.content.ovfManager.CreateDescriptor(vm, ovfDescParams)
root = ET.fromstring(ovf.ovfDescriptor)
new_id = list(root[0][1].attrib.values())[0][1:3]
ovfFiles = []
for vmdk in vmdks:
@ -109,14 +109,14 @@ class backup_vmdk(backup_generic):
ovfFiles.append(vim.OvfManager.OvfFile(size=os.path.getsize(vmdk['filename']), path=vmdk['filename'], deviceId=id))
ovfDescParams = vim.OvfManager.CreateDescriptorParams()
ovfDescParams.ovfFiles = ovfFiles;
ovfDescParams.ovfFiles = ovfFiles;
ovf = si.content.ovfManager.CreateDescriptor(vm, ovfDescParams)
ovf = si.content.ovfManager.CreateDescriptor(vm, ovfDescParams)
ovf_filename = vm.name+".ovf"
self.logger.debug("[%s] creating ovf file: %s" %(self.server_name,ovf_filename))
with open(ovf_filename, "w") as f:
f.write(ovf.ovfDescriptor)
with open(ovf_filename, "w") as f:
f.write(ovf.ovfDescriptor)
return ovf_filename
def create_ova(self,vm, vmdks, ovf_filename):
@ -131,9 +131,9 @@ class backup_vmdk(backup_generic):
def clone_vm(self,vm):
task = self.wait_task(vm.CreateSnapshot_Task(name="backup",description="Automatic backup "+datetime.now().strftime("%Y-%m-%d %H:%M:%s"),memory=False,quiesce=True))
snapshot=task.info.result
snapshot=task.info.result
prefix_vmclone = self.prefix_clone
clone_name = prefix_vmclone + vm.name
clone_name = prefix_vmclone + vm.name
datastore = '[%s]' % vm.datastore[0].name
@ -146,16 +146,16 @@ class backup_vmdk(backup_generic):
config = vim.vm.ConfigSpec(name=clone_name, memoryMB=vm.summary.config.memorySizeMB, numCPUs=vm.summary.config.numCpu, files=vmx_file)
hosts = datacenter.hostFolder.childEntity
resource_pool = hosts[0].resourcePool
resource_pool = hosts[0].resourcePool
self.wait_task(vmFolder.CreateVM_Task(config=config,pool=resource_pool))
self.wait_task(vmFolder.CreateVM_Task(config=config,pool=resource_pool))
new_vm = [x for x in vmFolder.childEntity if x.name == clone_name][0]
controller = vim.vm.device.VirtualDeviceSpec()
controller.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
controller.device = vim.vm.device.VirtualLsiLogicController(busNumber=0,sharedBus='noSharing')
controller.device.key = 0
controller.device = vim.vm.device.VirtualLsiLogicController(busNumber=0,sharedBus='noSharing')
controller.device.key = 0
i=0
vm_devices = []
@ -189,7 +189,7 @@ class backup_vmdk(backup_generic):
vm_devices.append(controller)
vm_devices.append(controller)
config.deviceChange=vm_devices
self.wait_task(new_vm.ReconfigVM_Task(config))
@ -198,7 +198,7 @@ class backup_vmdk(backup_generic):
def wait_task(self,task):
while task.info.state in ["queued", "running"]:
time.sleep(2)
self.logger.debug("[%s] %s",self.server_name,task.info.descriptionId)
self.logger.debug("[%s] %s",self.server_name,task.info.descriptionId)
return task
@ -213,7 +213,7 @@ class backup_vmdk(backup_generic):
else:
print('mkdir "%s"' % dest_dir)
else:
raise Exception('backup destination directory already exists : %s' % dest_dir)
raise Exception('backup destination directory already exists : %s' % dest_dir)
os.chdir(dest_dir)
user_esx, password_esx, null = open(self.password_file).read().split('\n')
@ -237,27 +237,27 @@ class backup_vmdk(backup_generic):
if vm.name == self.server_name:
vm_is_off = vm.summary.runtime.powerState == "poweredOff"
if str2bool(self.halt_vm):
vm.ShutdownGuest()
vm.ShutdownGuest()
vm_is_off = True
if vm_is_off:
vmdks = self.export_vmdks(vm)
ovf_filename = self.create_ovf(vm, vmdks)
ovf_filename = self.create_ovf(vm, vmdks)
else:
new_vm = self.clone_vm(vm)
vmdks = self.export_vmdks(new_vm)
ovf_filename = self.create_ovf(vm, vmdks)
self.wait_task(new_vm.Destroy_Task())
self.wait_task(new_vm.Destroy_Task())
if str2bool(self.create_ovafile):
ova_filename = self.create_ova(vm, vmdks, ovf_filename)
ova_filename = self.create_ova(vm, vmdks, ovf_filename)
if str2bool(self.halt_vm):
vm.PowerOnVM()
vm.PowerOnVM()
if os.path.exists(dest_dir):
for file in os.listdir(dest_dir):
for file in os.listdir(dest_dir):
stats['written_bytes'] += os.stat(file)[ST_SIZE]
stats['total_files_count'] += 1
stats['written_files_count'] += 1
@ -266,10 +266,10 @@ class backup_vmdk(backup_generic):
stats['written_bytes'] = 0
stats['backup_location'] = dest_dir
stats['log']='XVA backup from %s OK, %d bytes written' % (self.server_name,stats['written_bytes'])
stats['status']='OK'
stats['status']='OK'
except BaseException as e:
stats['status']='ERROR'
@ -279,4 +279,3 @@ class backup_vmdk(backup_generic):
register_driver(backup_vmdk)

View File

@ -20,12 +20,14 @@
from .common import *
import paramiko
from .common import *
class backup_xcp_metadata(backup_generic):
"""Backup metatdata of a xcp pool using xe pool-dump-database"""
type = 'xcp-dump-metadata'
type = 'xcp-dump-metadata'
required_params = ['type','server_name','private_key','backup_name']
def do_backup(self,stats):
@ -58,7 +60,7 @@ class backup_xcp_metadata(backup_generic):
stats['total_files_count']=1
stats['written_files_count']=1
stats['total_bytes']=os.stat(localpath).st_size
stats['written_bytes']=os.stat(localpath).st_size
stats['written_bytes']=os.stat(localpath).st_size
stats['log']='gzip dump of DB %s:%s (%d bytes) to %s' % (self.server_name,'xcp metadata dump', stats['written_bytes'], localpath)
stats['backup_location'] = localpath
stats['status']='OK'
@ -72,7 +74,7 @@ class backup_xcp_metadata(backup_generic):
filelist = os.listdir(self.backup_dir)
filelist.sort()
p = re.compile('^%s-(?P<date>\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}).dump.gz$' % self.server_name)
p = re.compile('^%s-(?P<date>\d{8,8}-\d{2,2}h\d{2,2}m\d{2,2}).dump.gz$' % self.server_name)
for item in filelist:
sr = p.match(item)
if sr:
@ -82,7 +84,7 @@ class backup_xcp_metadata(backup_generic):
self.logger.info('Registering %s from %s',file_name,fileisodate(file_name))
size_bytes = int(os.popen('du -sb "%s"' % file_name).read().split('\t')[0])
self.logger.debug(' Size in bytes : %i',size_bytes)
if not self.dry_run:
if not self.dry_run:
self.dbstat.add(self.backup_name,self.server_name,'',\
backup_start=start,backup_end=fileisodate(file_name),status='OK',total_bytes=size_bytes,backup_location=file_name)
else:

View File

@ -18,20 +18,23 @@
#
# -----------------------------------------------------------------------
import logging
import re
import os
import datetime
import urllib.request, urllib.parse, urllib.error
import socket
import tarfile
import hashlib
from stat import *
import logging
import os
import re
import socket
import ssl
import tarfile
import urllib.error
import urllib.parse
import urllib.request
from stat import *
import requests
from .common import *
from . import XenAPI
from .common import *
if hasattr(ssl, '_create_unverified_context'):
ssl._create_default_https_context = ssl._create_unverified_context

View File

@ -18,19 +18,19 @@
#
# -----------------------------------------------------------------------
from abc import ABC, abstractmethod
import os
import subprocess
import re
import logging
import datetime
import time
from iniparse import ConfigParser
import sqlite3
import shutil
import logging
import os
import re
import select
import shutil
import sqlite3
import subprocess
import sys
import time
from abc import ABC, abstractmethod
from iniparse import ConfigParser
try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko

View File

@ -18,23 +18,25 @@
#
# -----------------------------------------------------------------------
import os
import datetime
from .common import *
from . import XenAPI
import time
import logging
import re
import os.path
import os
import datetime
import select
import urllib.request, urllib.error, urllib.parse
import base64
import datetime
import logging
import os
import os.path
import re
import select
import socket
import ssl
import time
import urllib.error
import urllib.parse
import urllib.request
from stat import *
import ssl
if hasattr(ssl, '_create_unverified_context'):
from . import XenAPI
from .common import *
if hasattr(ssl, '_create_unverified_context'):
ssl._create_default_https_context = ssl._create_unverified_context
@ -44,23 +46,23 @@ class copy_vm_xcp(backup_generic):
required_params = backup_generic.required_params + ['server_name','storage_name','password_file','vm_name','network_name']
optional_params = backup_generic.optional_params + ['start_vm','max_copies', 'delete_snapshot', 'halt_vm']
start_vm = "no"
max_copies = 1
halt_vm = "no"
halt_vm = "no"
delete_snapshot = "yes"
def read_config(self,iniconf):
assert(isinstance(iniconf,ConfigParser))
backup_generic.read_config(self,iniconf)
backup_generic.read_config(self,iniconf)
if self.start_vm in 'no' and iniconf.has_option('global','start_vm'):
self.start_vm = iniconf.get('global','start_vm')
self.start_vm = iniconf.get('global','start_vm')
if self.max_copies == 1 and iniconf.has_option('global','max_copies'):
self.max_copies = iniconf.getint('global','max_copies')
self.max_copies = iniconf.getint('global','max_copies')
if self.delete_snapshot == "yes" and iniconf.has_option('global','delete_snapshot'):
self.delete_snapshot = iniconf.get('global','delete_snapshot')
def copy_vm_to_sr(self, vm_name, storage_name, dry_run, delete_snapshot="yes"):
user_xen, password_xen, null = open(self.password_file).read().split('\n')
session = XenAPI.Session('https://'+self.server_name)
@ -68,25 +70,25 @@ class copy_vm_xcp(backup_generic):
session.login_with_password(user_xen,password_xen)
except XenAPI.Failure as error:
msg,ip = error.details
if msg == 'HOST_IS_SLAVE':
server_name = ip
session = XenAPI.Session('https://'+server_name)
session.login_with_password(user_xen,password_xen)
session.login_with_password(user_xen,password_xen)
self.logger.debug("[%s] VM (%s) to backup in storage: %s",self.backup_name,vm_name,storage_name)
now = datetime.datetime.now()
#get storage opaqueRef
try:
storage = session.xenapi.SR.get_by_name_label(storage_name)[0]
except IndexError as error:
result = (1,"error get SR opaqueref %s"%(error))
return result
#get vm to copy opaqueRef
#get vm to copy opaqueRef
try:
vm = session.xenapi.VM.get_by_name_label(vm_name)[0]
except IndexError as error:
@ -99,96 +101,96 @@ class copy_vm_xcp(backup_generic):
except IndexError as error:
result = (1, "error get VM network opaqueref %s" % (error))
return result
if str2bool(self.halt_vm):
status_vm = session.xenapi.VM.get_power_state(vm)
status_vm = session.xenapi.VM.get_power_state(vm)
self.logger.debug("[%s] Status of VM: %s",self.backup_name,status_vm)
if status_vm == "Running":
self.logger.debug("[%s] Shutdown in progress",self.backup_name)
if dry_run:
print("session.xenapi.VM.clean_shutdown(vm)")
print("session.xenapi.VM.clean_shutdown(vm)")
else:
session.xenapi.VM.clean_shutdown(vm)
session.xenapi.VM.clean_shutdown(vm)
snapshot = vm
else:
#do the snapshot
#do the snapshot
self.logger.debug("[%s] Snapshot in progress",self.backup_name)
try:
snapshot = session.xenapi.VM.snapshot(vm,"tisbackup-%s"%(vm_name))
except XenAPI.Failure as error:
result = (1,"error when snapshot %s"%(error))
return result
#get snapshot opaqueRef
#get snapshot opaqueRef
snapshot = session.xenapi.VM.get_by_name_label("tisbackup-%s"%(vm_name))[0]
session.xenapi.VM.set_name_description(snapshot,"snapshot created by tisbackup on : %s"%(now.strftime("%Y-%m-%d %H:%M")))
vm_backup_name = "zzz-%s-"%(vm_name)
#Check if old backup exit
list_backups = []
for vm_ref in session.xenapi.VM.get_all():
name_lablel = session.xenapi.VM.get_name_label(vm_ref)
if vm_backup_name in name_lablel:
list_backups.append(name_lablel)
list_backups.sort()
if len(list_backups) >= 1:
# Shutting last backup if started
last_backup_vm = session.xenapi.VM.get_by_name_label(list_backups[-1])[0]
if not "Halted" in session.xenapi.VM.get_power_state(last_backup_vm):
self.logger.debug("[%s] Shutting down last backup vm : %s", self.backup_name, list_backups[-1] )
session.xenapi.VM.hard_shutdown(last_backup_vm)
# Delete oldest backup if exist
if len(list_backups) >= int(self.max_copies):
for i in range(len(list_backups)-int(self.max_copies)+1):
oldest_backup_vm = session.xenapi.VM.get_by_name_label(list_backups[i])[0]
if not "Halted" in session.xenapi.VM.get_power_state(oldest_backup_vm):
self.logger.debug("[%s] Shutting down old vm : %s", self.backup_name, list_backups[i] )
self.logger.debug("[%s] Shutting down old vm : %s", self.backup_name, list_backups[i] )
session.xenapi.VM.hard_shutdown(oldest_backup_vm)
try:
self.logger.debug("[%s] Deleting old vm : %s", self.backup_name, list_backups[i])
for vbd in session.xenapi.VM.get_VBDs(oldest_backup_vm):
if session.xenapi.VBD.get_type(vbd) == 'CD'and session.xenapi.VBD.get_record(vbd)['empty'] == False:
session.xenapi.VBD.eject(vbd)
session.xenapi.VBD.eject(vbd)
else:
vdi = session.xenapi.VBD.get_VDI(vbd)
if not 'NULL' in vdi:
session.xenapi.VDI.destroy(vdi)
session.xenapi.VM.destroy(oldest_backup_vm)
session.xenapi.VM.destroy(oldest_backup_vm)
except XenAPI.Failure as error:
result = (1,"error when destroy old backup vm %s"%(error))
return result
self.logger.debug("[%s] Copy %s in progress on %s",self.backup_name,vm_name,storage_name)
try:
backup_vm = session.xenapi.VM.copy(snapshot,vm_backup_name+now.strftime("%Y-%m-%d %H:%M"),storage)
except XenAPI.Failure as error:
result = (1,"error when copy %s"%(error))
return result
# define VM as a template
session.xenapi.VM.set_is_a_template(backup_vm,False)
#change the network of the new VM
try:
vifDestroy = session.xenapi.VM.get_VIFs(backup_vm)
except IndexError as error:
result = (1,"error get VIF opaqueref %s"%(error))
return result
for i in vifDestroy:
vifRecord = session.xenapi.VIF.get_record(i)
session.xenapi.VIF.destroy(i)
@ -216,13 +218,13 @@ class copy_vm_xcp(backup_generic):
except Exception as error:
result = (1,error)
return result
if self.start_vm in ['true', '1', 't', 'y', 'yes', 'oui']:
session.xenapi.VM.start(backup_vm,False,True)
session.xenapi.VM.set_name_description(backup_vm,"snapshot created by tisbackup on : %s"%(now.strftime("%Y-%m-%d %H:%M")))
size_backup = 0
for vbd in session.xenapi.VM.get_VBDs(backup_vm):
if session.xenapi.VBD.get_type(vbd) == 'CD' and session.xenapi.VBD.get_record(vbd)['empty'] == False:
@ -231,11 +233,11 @@ class copy_vm_xcp(backup_generic):
vdi = session.xenapi.VBD.get_VDI(vbd)
if not 'NULL' in vdi:
size_backup = size_backup + int(session.xenapi.VDI.get_record(vdi)['physical_utilisation'])
result = (0,size_backup)
if self.delete_snapshot == 'no':
return result
#Disable automatic boot
if 'auto_poweron' in session.xenapi.VM.get_other_config(backup_vm):
session.xenapi.VM.remove_from_other_config(backup_vm, "auto_poweron")
@ -258,25 +260,25 @@ class copy_vm_xcp(backup_generic):
if status_vm == "Running":
self.logger.debug("[%s] Starting in progress",self.backup_name)
if dry_run:
print("session.xenapi.VM.start(vm,False,True)")
print("session.xenapi.VM.start(vm,False,True)")
else:
session.xenapi.VM.start(vm,False,True)
return result
def do_backup(self,stats):
try:
timestamp = int(time.time())
cmd = self.copy_vm_to_sr(self.vm_name, self.storage_name, self.dry_run, delete_snapshot=self.delete_snapshot)
if cmd[0] == 0:
timeExec = int(time.time()) - timestamp
stats['log']='copy of %s to an other storage OK' % (self.backup_name)
stats['status']='OK'
stats['total_files_count'] = 1
stats['total_bytes'] = cmd[1]
stats['backup_location'] = self.storage_name
else:
stats['status']='ERROR'

View File

@ -3,18 +3,16 @@
# Copyright (c) 2007 Tim Lauridsen <tla@rasmil.dk>
# All Rights Reserved. See LICENSE-PSF & LICENSE for details.
from .ini import INIConfig, change_comment_syntax
from .compat import ConfigParser, RawConfigParser, SafeConfigParser
from .config import BasicConfig, ConfigNamespace
from .compat import RawConfigParser, ConfigParser, SafeConfigParser
from .configparser import (DEFAULTSECT, MAX_INTERPOLATION_DEPTH,
DuplicateSectionError, InterpolationDepthError,
InterpolationMissingOptionError,
InterpolationSyntaxError, NoOptionError,
NoSectionError)
from .ini import INIConfig, change_comment_syntax
from .utils import tidy
from .configparser import DuplicateSectionError, \
NoSectionError, NoOptionError, \
InterpolationMissingOptionError, \
InterpolationDepthError, \
InterpolationSyntaxError, \
DEFAULTSECT, MAX_INTERPOLATION_DEPTH
__all__ = [
'BasicConfig', 'ConfigNamespace',
'INIConfig', 'tidy', 'change_comment_syntax',

View File

@ -12,21 +12,18 @@ The underlying INIConfig object can be accessed as cfg.data
"""
import re
from .configparser import DuplicateSectionError, \
NoSectionError, NoOptionError, \
InterpolationMissingOptionError, \
InterpolationDepthError, \
InterpolationSyntaxError, \
DEFAULTSECT, MAX_INTERPOLATION_DEPTH
# These are imported only for compatiability.
# The code below does not reference them directly.
from .configparser import Error, InterpolationError, \
MissingSectionHeaderError, ParsingError
import six
from . import ini
# These are imported only for compatiability.
# The code below does not reference them directly.
from .configparser import (DEFAULTSECT, MAX_INTERPOLATION_DEPTH,
DuplicateSectionError, Error,
InterpolationDepthError, InterpolationError,
InterpolationMissingOptionError,
InterpolationSyntaxError, MissingSectionHeaderError,
NoOptionError, NoSectionError, ParsingError)
class RawConfigParser(object):

View File

@ -1,6 +1,6 @@
try:
from ConfigParser import *
# not all objects get imported with __all__
from ConfigParser import *
from ConfigParser import Error, InterpolationMissingOptionError
except ImportError:
from configparser import *

View File

@ -42,11 +42,11 @@ Example:
# Backward-compatiable with ConfigParser
import re
from .configparser import DEFAULTSECT, ParsingError, MissingSectionHeaderError
import six
from . import config
from .configparser import DEFAULTSECT, MissingSectionHeaderError, ParsingError
class LineType(object):

View File

@ -1,5 +1,5 @@
from . import compat
from .ini import LineContainer, EmptyLine
from .ini import EmptyLine, LineContainer
def tidy(cfg):

10
pyproject.toml Normal file
View File

@ -0,0 +1,10 @@
[tool.black]
line-length = 140
[tool.ruff]
# Allow lines to be as long as 120.
line-length = 140
indent-width = 4
[tool.ruff.lint]
ignore = ["F401","F403","F405","E402"]

7
requirements.txt Normal file → Executable file
View File

@ -1,3 +1,10 @@
six
requests
paramiko
pexpect
flask
simplejson
huey
iniparse
redis
peewee

View File

@ -7,9 +7,8 @@ mkdir -p BUILD RPMS
VERSION=`git rev-list HEAD --count`
VERSION=`git rev-list HEAD --count`
echo $VERSION > __VERSION__
rpmbuild -bb --buildroot $PWD/builddir -v --clean tis-tisbackup.spec
cp RPMS/*/*.rpm .

View File

@ -15,7 +15,7 @@ Source0: ../
Prefix: /
%if "%{rhel}" == "8"
Requires: unzip rsync python3-paramiko python3-pyvmomi nfs-utils python3-flask python3-simplejson autofs python3-pexpect
Requires: unzip rsync python3-paramiko python3-pyvmomi nfs-utils python3-flask python3-simplejson autofs python3-pexpect
%endif
%if "%{rhel}" == "7"
Requires: unzip rsync python36-paramiko python3-pyvmomi nfs-utils python3-flask python3-simplejson autofs pexpect

View File

@ -14,5 +14,3 @@ else
sleep 3
fi
echo $(date +%Y-%m-%d\ %H:%M:%S) : Fin Export TISBackup sur Disque USB : $target >> /var/log/tisbackup.log

View File

@ -20,7 +20,7 @@ maximum_backup_age=30
;type=rsync+ssh
;server_name=srvzimbra
;remote_dir=/
;exclude_list="/proc/**","/sys/**","/dev/**"
;exclude_list="/proc/**","/sys/**","/dev/**"
;private_key=/root/.ssh/id_rsa
;ssh_port = 22
@ -95,4 +95,3 @@ maximum_backup_age=30
;type=xcp-dump-metadata
;server_name=srvxen1
;private_key=/root/.ssh/id_rsa

View File

@ -18,4 +18,3 @@ password_file=/home/homes/ssamson/tisbackup-pra/xen_passwd
network_name=net-test
#start_vm=no
#max_copies=3

View File

@ -4,4 +4,3 @@
# m h dom mon dow user command
30 22 * * * root /opt/tisbackup/tisbackup.py -c /etc/tis/tisbackup-config.ini backup >> /var/log/tisbackup.log 2>&1
30 12 * * * root /opt/tisbackup/tisbackup.py -c /etc/tis/tisbackup-config.ini cleanup >> /var/log/tisbackup.log 2>&1

View File

@ -1,5 +1,5 @@
[general]
config_tisbackup= /etc/tis/tisbackup-config.ini
sections=
sections=
ADMIN_EMAIL=technique@tranquil-it-systems.fr
base_config_dir= /etc/tis/

View File

@ -95,4 +95,3 @@ case "$1" in
esac
exit 0

View File

@ -1,9 +1,9 @@
[Unit]
Description=tisbackup
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/tisbackup/tisbackup_gui.py
[Install]
WantedBy=multi-user.target

View File

@ -95,4 +95,3 @@ case "$1" in
esac
exit 0

View File

@ -1,9 +1,9 @@
[Unit]
Description=tisbackup
[Service]
Type=simple
ExecStart=huey_consumer.py -n tisbackup_gui.huey
WorkingDirectory=/opt/tisbackup
WorkingDirectory=/opt/tisbackup
[Install]
WantedBy=multi-user.target

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

2
static/js/jquery.js vendored

File diff suppressed because one or more lines are too long

View File

@ -2,4 +2,4 @@ $(document).ready(function () {
$('[data-toggle="offcanvas"]').click(function () {
$('.row-offcanvas').toggleClass('active')
});
});
});

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,8 @@
from huey import RedisHuey
import os
import logging
import os
from huey import RedisHuey
from tisbackup import tis_backup
huey = RedisHuey('tisbackup', host='localhost')

View File

@ -61,7 +61,7 @@
</div>
</div>
</div>
{% endif %}
{% if backup_list['rsync_list']|count != 0 %}

View File

@ -9,9 +9,9 @@
{% for message in messages %}
<div class="alert alert-success fade in">
<a href="#" class="close" data-dismiss="alert">&times;</a>
<strong>Success!</strong> {{ message }}
<strong>Success!</strong> {{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
@ -19,7 +19,7 @@
<p>
<div class="alert alert-danger fade in"><strong>Error</strong>: {{ error }}</div>
<div class="alert alert-warning"><strong>Notice</strong>: {{ info }}</div>
<h4>Also, you can contact your <a href="mailto:{{ email }}?Subject=TISBACKUP%20Export"> System Administrator</a> for more details </h4>
</p>
{% elif not start %}
@ -31,28 +31,28 @@
$("#backup").submit();
};
});
});
});
$('#selectall').click(function(event) { //on click
if(this.checked) { // check select status
$('.checkbox1').each(function() { //loop through each checkbox
this.checked = true; //select all checkboxes with class "checkbox1"
this.checked = true; //select all checkboxes with class "checkbox1"
});
}else{
$('.checkbox1').each(function() { //loop through each checkbox
this.checked = false; //deselect all checkboxes with class "checkbox1"
});
this.checked = false; //deselect all checkboxes with class "checkbox1"
});
}
});
});
</script>
<form id="backup" action='/export_backup'>
<p> Select backups to save : <br/>
<p> Select backups to save : <br/>
<div class="row">
<div class="checkbox"><label><input type="checkbox" class="checkbox1" id="selectall" checked>Select all</label></div>
<div class="checkbox"><label><input type="checkbox" class="checkbox1" id="selectall" checked>Select all</label></div>
{% for entry in sections|sort %}
<div class="col-xs-6 col-md-4">
<div class="form-group">
<div class="checkbox"><label><input type="checkbox" name="sections" class="checkbox1" value="{{entry}}" checked>{{entry}}</label></div>
<div class="checkbox"><label><input type="checkbox" name="sections" class="checkbox1" value="{{entry}}" checked>{{entry}}</label></div>
</div>
</div>
{% endfor %}
@ -65,7 +65,7 @@
</div>
</form>
{% else %}
<h2 class="title">Backups is running: </h2>
<table id="table" class='table'>
@ -79,7 +79,7 @@
</table>
<script>
//Refresh periode in seconds
var refresh = 5;
@ -97,7 +97,7 @@ function status(){
done = false;
}else{
$('tbody').append('<td>'+val.status+'</td>');
done = data.finish;
}
$('#table-design').append('</tr>');

View File

@ -24,7 +24,7 @@
"aTargets": [ 0 ],
"mData": "backup_start",
"mRender": function ( data, type, full ) {
var d = new Date(data);
var d = new Date(data);
return d.getFullYear()+"/"+(d.getMonth()+1)+"/"+d.getDate()+" "+d.toLocaleTimeString();
}
},
@ -32,7 +32,7 @@
"aTargets": [ 1 ],
"mData": "backup_start",
"mRender": function ( data, type, full ) {
var d = new Date(data);
var d = new Date(data);
return d.getFullYear()+"/"+(d.getMonth()+1)+"/"+d.getDate()+" "+d.toLocaleTimeString();
}
},
@ -79,7 +79,7 @@
$('#inputDatabaseName').keyup(function () { delay(function(){ oTable.fnLengthChange($('#inputDatabaseName').val() ); }, 300 )});
$(".dataTables_length").remove()
var nb_row = GetURLParameter('row');
if (nb_row ){
if (nb_row ){
oTable.fnLengthChange( nb_row) ;
$('#inputDatabaseName').val(nb_row);
}
@ -96,7 +96,7 @@
{
/* Get the DataTables object again - this is not a recreation, just a get of the object */
var oTable = $('#table-design').dataTable();
var bVis = oTable.fnSettings().aoColumns[iCol].bVisible;
oTable.fnSetColumnVis( iCol, bVis ? false : true );
}
@ -111,10 +111,10 @@
{
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam)
if (sParameterName[0] == sParam)
{
return sParameterName[1];
}
@ -135,7 +135,7 @@
{
oSettings._iDisplayLength = iDisplay;
oSettings.oApi._fnCalculateEnd( oSettings );
/* If we have space to show extra rows (backing up from the end point - then do so */
if ( oSettings._iDisplayEnd == oSettings.aiDisplay.length )
{
@ -145,14 +145,14 @@
oSettings._iDisplayStart = 0;
}
}
if ( oSettings._iDisplayLength == -1 )
{
oSettings._iDisplayStart = 0;
}
oSettings.oApi._fnDraw( oSettings );
if ( oSettings.aanFeatures.l )
{
$('select', oSettings.aanFeatures.l).val( iDisplay );
@ -179,7 +179,7 @@
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
@ -204,7 +204,7 @@
<div class="checkbox"><label><input type="checkbox" onclick="fnShowHide( 4 );" checked> Backup duration</label></div>
</div>
</div>
<div class="col-xs-6 col-md-4">
<div class="form-group">
<div class="checkbox"><label><input type="checkbox" onclick="fnShowHide( 6 );"checked> Written bytes</label></div>
@ -213,9 +213,9 @@
<div class="checkbox"><label><input type="checkbox" onclick="fnShowHide( 9 );"/> Total bytes</label></div>
</div>
</div>
<div class="col-xs-6 col-md-4">
<div class="form-group">
<div class="form-group">
<div class="checkbox"><label><input type="checkbox" onclick="fnShowHide( 10 );"> Backup location</label></div>
<div class="checkbox"><label><input type="checkbox" onclick="fnShowHide( 11);">Description </label></div>
<div class="checkbox"><label><input type="checkbox" onclick="fnShowHide( 12);"> Log</label></div>
@ -225,7 +225,7 @@
</div>
</form>
</p>
{% endblock %}

View File

@ -55,7 +55,7 @@
{% endblock %}
</div><!--/row-->
<hr>
<footer>
@ -80,7 +80,7 @@
if ( data.configs.length > 1){
$('#choix_conf').removeClass('hidden');
$("#choix_conf").show();
$.each(data.configs, function(key,val){
if (key == data.config_number)
$('#choix_conf').append('<option vaulue="'+key+'" selected>'+val+'</option>');
@ -89,7 +89,7 @@
});
}
});
$( "#choix_conf" ).change(function() {
$.get( "/config_number/"+this.selectedIndex, function( data ) {location.reload();});
});
@ -98,4 +98,3 @@
</script>
</body></html>

View File

@ -18,41 +18,43 @@
#
# -----------------------------------------------------------------------
import datetime
import subprocess
import os,sys
import os
import sys
from os.path import isfile, join
tisbackup_root_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0,os.path.join(tisbackup_root_dir,'lib'))
sys.path.insert(0,os.path.join(tisbackup_root_dir,'libtisbackup'))
sys.path.insert(0, os.path.join(tisbackup_root_dir, "lib"))
sys.path.insert(0, os.path.join(tisbackup_root_dir, "libtisbackup"))
from iniparse import ini,ConfigParser
from optparse import OptionParser
import re
import getopt
import os.path
import logging
import errno
from libtisbackup.common import *
import logging
import os.path
from optparse import OptionParser
from iniparse import ConfigParser, ini
from libtisbackup.backup_mysql import backup_mysql
from libtisbackup.backup_rsync import backup_rsync
from libtisbackup.backup_rsync import backup_rsync_ssh
#from libtisbackup.backup_oracle import backup_oracle
from libtisbackup.backup_rsync_btrfs import backup_rsync_btrfs
from libtisbackup.backup_rsync_btrfs import backup_rsync__btrfs_ssh
from libtisbackup.backup_pgsql import backup_pgsql
from libtisbackup.backup_xva import backup_xva
#from libtisbackup.backup_vmdk import backup_vmdk
#from libtisbackup.backup_switch import backup_switch
# from libtisbackup.backup_vmdk import backup_vmdk
# from libtisbackup.backup_switch import backup_switch
from libtisbackup.backup_null import backup_null
from libtisbackup.backup_xcp_metadata import backup_xcp_metadata
from libtisbackup.copy_vm_xcp import copy_vm_xcp
#from libtisbackup.backup_sqlserver import backup_sqlserver
from libtisbackup.backup_pgsql import backup_pgsql
from libtisbackup.backup_rsync import backup_rsync, backup_rsync_ssh
# from libtisbackup.backup_oracle import backup_oracle
from libtisbackup.backup_rsync_btrfs import backup_rsync__btrfs_ssh, backup_rsync_btrfs
# from libtisbackup.backup_sqlserver import backup_sqlserver
from libtisbackup.backup_samba4 import backup_samba4
from libtisbackup.backup_xcp_metadata import backup_xcp_metadata
from libtisbackup.backup_xva import backup_xva
from libtisbackup.common import *
from libtisbackup.copy_vm_xcp import copy_vm_xcp
__version__="2.0"
__version__ = "2.0"
usage="""\
usage = """\
%prog -c configfile action
TIS Files Backup system.
@ -67,52 +69,75 @@ action is either :
exportbackup : copy lastest OK backups from local to location defned by --exportdir parameter
register_existing : scan backup directories and add missing backups to database"""
version="VERSION"
version = "VERSION"
parser = OptionParser(usage=usage, version="%prog " + version)
parser.add_option(
"-c", "--config", dest="config", default="/etc/tis/tisbackup-config.ini", help="Config file full path (default: %default)"
)
parser.add_option("-d", "--dry-run", dest="dry_run", default=False, action="store_true", help="Dry run (default: %default)")
parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help="More information (default: %default)")
parser.add_option(
"-s", "--sections", dest="sections", default="", help="Comma separated list of sections (backups) to process (default: All)"
)
parser.add_option(
"-l",
"--loglevel",
dest="loglevel",
default="info",
type="choice",
choices=["debug", "warning", "info", "error", "critical"],
metavar="LOGLEVEL",
help="Loglevel (default: %default)",
)
parser.add_option("-n", "--len", dest="statscount", default=30, type="int", help="Number of lines to list for dumpstat (default: %default)")
parser.add_option(
"-b",
"--backupdir",
dest="backup_base_dir",
default="",
help="Base directory for all backups (default: [global] backup_base_dir in config file)",
)
parser.add_option(
"-x", "--exportdir", dest="exportdir", default="", help="Directory where to export latest backups with exportbackup (nodefault)"
)
parser=OptionParser(usage=usage,version="%prog " + version)
parser.add_option("-c","--config", dest="config", default='/etc/tis/tisbackup-config.ini', help="Config file full path (default: %default)")
parser.add_option("-d","--dry-run", dest="dry_run", default=False, action='store_true', help="Dry run (default: %default)")
parser.add_option("-v","--verbose", dest="verbose", default=False, action='store_true', help="More information (default: %default)")
parser.add_option("-s","--sections", dest="sections", default='', help="Comma separated list of sections (backups) to process (default: All)")
parser.add_option("-l","--loglevel", dest="loglevel", default='info', type='choice', choices=['debug','warning','info','error','critical'], metavar='LOGLEVEL',help="Loglevel (default: %default)")
parser.add_option("-n","--len", dest="statscount", default=30, type='int', help="Number of lines to list for dumpstat (default: %default)")
parser.add_option("-b","--backupdir", dest="backup_base_dir", default='', help="Base directory for all backups (default: [global] backup_base_dir in config file)")
parser.add_option("-x","--exportdir", dest="exportdir", default='', help="Directory where to export latest backups with exportbackup (nodefault)")
class tis_backup:
logger = logging.getLogger('tisbackup')
logger = logging.getLogger("tisbackup")
def __init__(self,dry_run=False,verbose=False,backup_base_dir=''):
def __init__(self, dry_run=False, verbose=False, backup_base_dir=""):
self.dry_run = dry_run
self.verbose = verbose
self.backup_base_dir = backup_base_dir
self.backup_base_dir = ''
self.backup_base_dir = ""
self.backup_list = []
self.dry_run = dry_run
self.verbose=False
self.verbose = False
def read_ini_file(self,filename):
def read_ini_file(self, filename):
ini.change_comment_syntax()
cp = ConfigParser()
cp.read(filename)
if not self.backup_base_dir:
self.backup_base_dir = cp.get('global','backup_base_dir')
self.backup_base_dir = cp.get("global", "backup_base_dir")
if not os.path.isdir(self.backup_base_dir):
self.logger.info('Creating backup directory %s' % self.backup_base_dir)
self.logger.info("Creating backup directory %s" % self.backup_base_dir)
os.makedirs(self.backup_base_dir)
self.logger.debug("backup directory : " + self.backup_base_dir)
self.dbstat = BackupStat(os.path.join(self.backup_base_dir,'log','tisbackup.sqlite'))
self.dbstat = BackupStat(os.path.join(self.backup_base_dir, "log", "tisbackup.sqlite"))
for section in cp.sections():
if (section != 'global'):
if section != "global":
self.logger.debug("reading backup config " + section)
backup_item = None
type = cp.get(section,'type')
type = cp.get(section, "type")
backup_item = backup_drivers[type](backup_name=section,
backup_dir=os.path.join(self.backup_base_dir,section),dbstat=self.dbstat,dry_run=self.dry_run)
backup_item = backup_drivers[type](
backup_name=section, backup_dir=os.path.join(self.backup_base_dir, section), dbstat=self.dbstat, dry_run=self.dry_run
)
backup_item.read_config(cp)
backup_item.verbose = self.verbose
@ -122,35 +147,34 @@ class tis_backup:
# TODO socket.gethostbyaddr('64.236.16.20')
# TODO limit backup to one backup on the command line
def checknagios(self,sections=[]):
def checknagios(self, sections=[]):
try:
if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list]
self.logger.debug('Start of check nagios for %s' % (','.join(sections),))
self.logger.debug("Start of check nagios for %s" % (",".join(sections),))
try:
worst_nagiosstatus = None
ok = []
warning = []
critical = []
unknown = []
nagiosoutput = ''
nagiosoutput = ""
for backup_item in self.backup_list:
if not sections or backup_item.backup_name in sections:
(nagiosstatus,log) = backup_item.checknagios()
(nagiosstatus, log) = backup_item.checknagios()
if nagiosstatus == nagiosStateCritical:
critical.append((backup_item.backup_name,log))
elif nagiosstatus == nagiosStateWarning :
warning.append((backup_item.backup_name,log))
critical.append((backup_item.backup_name, log))
elif nagiosstatus == nagiosStateWarning:
warning.append((backup_item.backup_name, log))
elif nagiosstatus == nagiosStateOk:
ok.append((backup_item.backup_name,log))
ok.append((backup_item.backup_name, log))
else:
unknown.append((backup_item.backup_name,log))
self.logger.debug('[%s] nagios:"%i" log: %s',backup_item.backup_name,nagiosstatus,log)
unknown.append((backup_item.backup_name, log))
self.logger.debug('[%s] nagios:"%i" log: %s', backup_item.backup_name, nagiosstatus, log)
if not ok and not critical and not unknown and not warning:
self.logger.debug('Nothing processed')
self.logger.debug("Nothing processed")
worst_nagiosstatus = nagiosStateUnknown
nagiosoutput = 'UNKNOWN : Unknown backup sections "%s"' % sections
@ -159,156 +183,154 @@ class tis_backup:
if unknown:
if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateUnknown
nagiosoutput = 'UNKNOWN status backups %s' % (','.join([b[0] for b in unknown]))
nagiosoutput = "UNKNOWN status backups %s" % (",".join([b[0] for b in unknown]))
globallog.extend(unknown)
if critical:
if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateCritical
nagiosoutput = 'CRITICAL backups %s' % (','.join([b[0] for b in critical]))
nagiosoutput = "CRITICAL backups %s" % (",".join([b[0] for b in critical]))
globallog.extend(critical)
if warning:
if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateWarning
nagiosoutput = 'WARNING backups %s' % (','.join([b[0] for b in warning]))
nagiosoutput = "WARNING backups %s" % (",".join([b[0] for b in warning]))
globallog.extend(warning)
if ok:
if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateOk
nagiosoutput = 'OK backups %s' % (','.join([b[0] for b in ok]))
nagiosoutput = "OK backups %s" % (",".join([b[0] for b in ok]))
globallog.extend(ok)
if worst_nagiosstatus == nagiosStateOk:
nagiosoutput = 'ALL backups OK %s' % (','.join(sections))
nagiosoutput = "ALL backups OK %s" % (",".join(sections))
except BaseException as e:
worst_nagiosstatus = nagiosStateCritical
nagiosoutput = 'EXCEPTION',"Critical : %s" % str(e)
nagiosoutput = "EXCEPTION", "Critical : %s" % str(e)
raise
finally:
self.logger.debug('worst nagios status :"%i"',worst_nagiosstatus)
print('%s (tisbackup V%s)' %(nagiosoutput,version))
print('\n'.join(["[%s]:%s" % (l[0],l[1]) for l in globallog]))
self.logger.debug('worst nagios status :"%i"', worst_nagiosstatus)
print("%s (tisbackup V%s)" % (nagiosoutput, version))
print("\n".join(["[%s]:%s" % (log_elem[0], log_elem[1]) for log_elem in globallog]))
sys.exit(worst_nagiosstatus)
def process_backup(self,sections=[]):
def process_backup(self, sections=[]):
processed = []
errors = []
if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list]
self.logger.info('Processing backup for %s' % (','.join(sections)) )
self.logger.info("Processing backup for %s" % (",".join(sections)))
for backup_item in self.backup_list:
if not sections or backup_item.backup_name in sections:
try:
assert(isinstance(backup_item,backup_generic))
self.logger.info('Processing [%s]',(backup_item.backup_name))
assert isinstance(backup_item, backup_generic)
self.logger.info("Processing [%s]", (backup_item.backup_name))
stats = backup_item.process_backup()
processed.append((backup_item.backup_name,stats))
processed.append((backup_item.backup_name, stats))
except BaseException as e:
self.logger.critical('Backup [%s] processed with error : %s',backup_item.backup_name,e)
errors.append((backup_item.backup_name,str(e)))
self.logger.critical("Backup [%s] processed with error : %s", backup_item.backup_name, e)
errors.append((backup_item.backup_name, str(e)))
if not processed and not errors:
self.logger.critical('No backup properly finished or processed')
self.logger.critical("No backup properly finished or processed")
else:
if processed:
self.logger.info('Backup processed : %s' , ",".join([b[0] for b in processed]))
self.logger.info("Backup processed : %s", ",".join([b[0] for b in processed]))
if errors:
self.logger.error('Backup processed with errors: %s' , ",".join([b[0] for b in errors]))
self.logger.error("Backup processed with errors: %s", ",".join([b[0] for b in errors]))
def export_backups(self,sections=[],exportdir=''):
def export_backups(self, sections=[], exportdir=""):
processed = []
errors = []
if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list]
self.logger.info('Exporting OK backups for %s to %s' % (','.join(sections),exportdir) )
self.logger.info("Exporting OK backups for %s to %s" % (",".join(sections), exportdir))
for backup_item in self.backup_list:
if backup_item.backup_name in sections:
try:
assert(isinstance(backup_item,backup_generic))
self.logger.info('Processing [%s]',(backup_item.backup_name))
assert isinstance(backup_item, backup_generic)
self.logger.info("Processing [%s]", (backup_item.backup_name))
stats = backup_item.export_latestbackup(destdir=exportdir)
processed.append((backup_item.backup_name,stats))
processed.append((backup_item.backup_name, stats))
except BaseException as e:
self.logger.critical('Export Backup [%s] processed with error : %s',backup_item.backup_name,e)
errors.append((backup_item.backup_name,str(e)))
self.logger.critical("Export Backup [%s] processed with error : %s", backup_item.backup_name, e)
errors.append((backup_item.backup_name, str(e)))
if not processed and not errors:
self.logger.critical('No export backup properly finished or processed')
self.logger.critical("No export backup properly finished or processed")
else:
if processed:
self.logger.info('Export Backups processed : %s' , ",".join([b[0] for b in processed]))
self.logger.info("Export Backups processed : %s", ",".join([b[0] for b in processed]))
if errors:
self.logger.error('Export Backups processed with errors: %s' , ",".join([b[0] for b in errors]))
self.logger.error("Export Backups processed with errors: %s", ",".join([b[0] for b in errors]))
def retry_failed_backups(self,maxage_hours=30):
def retry_failed_backups(self, maxage_hours=30):
processed = []
errors = []
# before mindate, backup is too old
mindate = datetime2isodate((datetime.datetime.now() - datetime.timedelta(hours=maxage_hours)))
failed_backups = self.dbstat.query("""\
failed_backups = self.dbstat.query(
"""\
select distinct backup_name as bname
from stats
where status="OK" and backup_start>=?""",(mindate,))
defined_backups = list(map(lambda f:f.backup_name, [ x for x in self.backup_list if not isinstance(x, backup_null) ]))
failed_backups_names = set(defined_backups) - set([b['bname'] for b in failed_backups if b['bname'] in defined_backups])
where status="OK" and backup_start>=?""",
(mindate,),
)
defined_backups = list(map(lambda f: f.backup_name, [x for x in self.backup_list if not isinstance(x, backup_null)]))
failed_backups_names = set(defined_backups) - set([b["bname"] for b in failed_backups if b["bname"] in defined_backups])
if failed_backups_names:
self.logger.info('Processing backup for %s',','.join(failed_backups_names))
self.logger.info("Processing backup for %s", ",".join(failed_backups_names))
for backup_item in self.backup_list:
if backup_item.backup_name in failed_backups_names:
try:
assert(isinstance(backup_item,backup_generic))
self.logger.info('Processing [%s]',(backup_item.backup_name))
assert isinstance(backup_item, backup_generic)
self.logger.info("Processing [%s]", (backup_item.backup_name))
stats = backup_item.process_backup()
processed.append((backup_item.backup_name,stats))
processed.append((backup_item.backup_name, stats))
except BaseException as e:
self.logger.critical('Backup [%s] not processed, error : %s',backup_item.backup_name,e)
errors.append((backup_item.backup_name,str(e)))
self.logger.critical("Backup [%s] not processed, error : %s", backup_item.backup_name, e)
errors.append((backup_item.backup_name, str(e)))
if not processed and not errors:
self.logger.critical('No backup properly finished or processed')
self.logger.critical("No backup properly finished or processed")
else:
if processed:
self.logger.info('Backup processed : %s' , ",".join([b[0] for b in errors]))
self.logger.info("Backup processed : %s", ",".join([b[0] for b in errors]))
if errors:
self.logger.error('Backup processed with errors: %s' , ",".join([b[0] for b in errors]))
self.logger.error("Backup processed with errors: %s", ",".join([b[0] for b in errors]))
else:
self.logger.info('No recent failed backups found in database')
self.logger.info("No recent failed backups found in database")
def cleanup_backup_section(self,sections = []):
log = ''
def cleanup_backup_section(self, sections=[]):
processed = False
if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list]
self.logger.info('Processing cleanup for %s' % (','.join(sections)) )
self.logger.info("Processing cleanup for %s" % (",".join(sections)))
for backup_item in self.backup_list:
if backup_item.backup_name in sections:
try:
assert(isinstance(backup_item,backup_generic))
self.logger.info('Processing cleanup of [%s]',(backup_item.backup_name))
assert isinstance(backup_item, backup_generic)
self.logger.info("Processing cleanup of [%s]", (backup_item.backup_name))
backup_item.cleanup_backup()
processed = True
except BaseException as e:
self.logger.critical('Cleanup of [%s] not processed, error : %s',backup_item.backup_name,e)
self.logger.critical("Cleanup of [%s] not processed, error : %s", backup_item.backup_name, e)
if not processed:
self.logger.critical('No cleanup properly finished or processed')
self.logger.critical("No cleanup properly finished or processed")
def register_existingbackups(self,sections = []):
def register_existingbackups(self, sections=[]):
if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list]
self.logger.info('Append existing backups to database...')
self.logger.info("Append existing backups to database...")
for backup_item in self.backup_list:
if backup_item.backup_name in sections:
backup_item.register_existingbackups()
@ -316,26 +338,26 @@ class tis_backup:
def html_report(self):
for backup_item in self.backup_list:
if not section or section == backup_item.backup_name:
assert(isinstance(backup_item,backup_generic))
assert isinstance(backup_item, backup_generic)
if not maxage_hours:
maxage_hours = backup_item.maximum_backup_age
(nagiosstatus,log) = backup_item.checknagios(maxage_hours=maxage_hours)
globallog.append('[%s] %s' % (backup_item.backup_name,log))
self.logger.debug('[%s] nagios:"%i" log: %s',backup_item.backup_name,nagiosstatus,log)
processed = True
if nagiosstatus >= worst_nagiosstatus:
worst_nagiosstatus = nagiosstatus
(nagiosstatus, log) = backup_item.checknagios(maxage_hours=maxage_hours)
globallog.append("[%s] %s" % (backup_item.backup_name, log))
self.logger.debug('[%s] nagios:"%i" log: %s', backup_item.backup_name, nagiosstatus, log)
# processed = True
# if nagiosstatus >= worst_nagiosstatus:
# worst_nagiosstatus = nagiosstatus
def main():
(options,args)=parser.parse_args()
(options, args) = parser.parse_args()
if len(args) != 1:
print("ERROR : You must provide one action to perform")
parser.print_usage()
sys.exit(2)
backup_start_date = datetime.datetime.now().strftime('%Y%m%d-%Hh%Mm%S')
backup_start_date = datetime.datetime.now().strftime("%Y%m%d-%Hh%Mm%S")
# options
action = args[0]
@ -344,23 +366,23 @@ def main():
print(backup_drivers[t].get_help())
sys.exit(0)
config_file =options.config
config_file = options.config
dry_run = options.dry_run
verbose = options.verbose
loglevel = options.loglevel
# setup Logger
logger = logging.getLogger('tisbackup')
logger = logging.getLogger("tisbackup")
hdlr = logging.StreamHandler()
hdlr.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
hdlr.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
logger.addHandler(hdlr)
# set loglevel
if loglevel in ('debug','warning','info','error','critical'):
if loglevel in ("debug", "warning", "info", "error", "critical"):
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % loglevel)
raise ValueError("Invalid log level: %s" % loglevel)
logger.setLevel(numeric_level)
# Config file
@ -371,36 +393,36 @@ def main():
cp = ConfigParser()
cp.read(config_file)
backup_base_dir = options.backup_base_dir or cp.get('global','backup_base_dir')
log_dir = os.path.join(backup_base_dir,'log')
backup_base_dir = options.backup_base_dir or cp.get("global", "backup_base_dir")
log_dir = os.path.join(backup_base_dir, "log")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# if we run the nagios check, we don't create log file, everything is piped to stdout
if action!='checknagios':
if action != "checknagios":
try:
hdlr = logging.FileHandler(os.path.join(log_dir,'tisbackup_%s.log' % (backup_start_date)))
hdlr.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
hdlr = logging.FileHandler(os.path.join(log_dir, "tisbackup_%s.log" % (backup_start_date)))
hdlr.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
logger.addHandler(hdlr)
except IOError as e:
if action == 'cleanup' and e.errno == errno.ENOSPC:
if action == "cleanup" and e.errno == errno.ENOSPC:
logger.warning("No space left on device, disabling file logging.")
else:
raise e
# Main
backup = tis_backup(dry_run=dry_run,verbose=verbose,backup_base_dir=backup_base_dir)
backup = tis_backup(dry_run=dry_run, verbose=verbose, backup_base_dir=backup_base_dir)
backup.read_ini_file(config_file)
backup_sections = options.sections.split(',') if options.sections else []
backup_sections = options.sections.split(",") if options.sections else []
all_sections = [backup_item.backup_name for backup_item in backup.backup_list]
if not backup_sections:
backup_sections = all_sections
else:
for b in backup_sections:
if not b in all_sections:
raise Exception('Section %s is not defined in config file' % b)
if b not in all_sections:
raise Exception("Section %s is not defined in config file" % b)
if dry_run:
logger.warning("WARNING : DRY RUN, nothing will be done, just printing on screen...")
@ -409,23 +431,22 @@ def main():
backup.process_backup(backup_sections)
elif action == "exportbackup":
if not options.exportdir:
raise Exception('No export directory supplied dor exportbackup action')
backup.export_backups(backup_sections,options.exportdir)
raise Exception("No export directory supplied dor exportbackup action")
backup.export_backups(backup_sections, options.exportdir)
elif action == "cleanup":
backup.cleanup_backup_section(backup_sections)
elif action == "checknagios":
backup.checknagios(backup_sections)
elif action == "dumpstat":
for s in backup_sections:
backup.dbstat.last_backups(s,count=options.statscount)
backup.dbstat.last_backups(s, count=options.statscount)
elif action == "retryfailed":
backup.retry_failed_backups()
elif action == "register_existing":
backup.register_existingbackups(backup_sections)
else:
logger.error('Unhandled action "%s", quitting...',action)
logger.error('Unhandled action "%s", quitting...', action)
sys.exit(1)

View File

@ -17,89 +17,89 @@
# along with TISBackup. If not, see <http://www.gnu.org/licenses/>.
#
# -----------------------------------------------------------------------
import os,sys
import os
import sys
from os.path import isfile, join
tisbackup_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__)))
sys.path.append(os.path.join(tisbackup_root_dir,'lib'))
sys.path.append(os.path.join(tisbackup_root_dir,'libtisbackup'))
sys.path.append(os.path.join(tisbackup_root_dir, "lib"))
sys.path.append(os.path.join(tisbackup_root_dir, "libtisbackup"))
from shutil import *
from iniparse import ConfigParser,RawConfigParser
from libtisbackup.common import *
import time
from flask import request, Flask, session, g, appcontext_pushed, redirect, url_for, abort, render_template, flash, jsonify, Response
from urllib.parse import urlparse
import json
import glob
import time
from config import huey
from tasks import run_export_backup, get_task, set_task
from tisbackup import tis_backup
import json
import logging
import re
import time
from shutil import *
from urllib.parse import urlparse
from flask import Flask, Response, abort, appcontext_pushed, flash, g, jsonify, redirect, render_template, request, session, url_for
from iniparse import ConfigParser, RawConfigParser
from config import huey
from libtisbackup.common import *
from tasks import get_task, run_export_backup, set_task
from tisbackup import tis_backup
cp = ConfigParser()
cp.read("/etc/tis/tisbackup_gui.ini")
CONFIG = cp.get('general','config_tisbackup').split(",")
SECTIONS = cp.get('general','sections')
ADMIN_EMAIL = cp.get('general','ADMIN_EMAIL')
BASE_DIR = cp.get('general','base_config_dir')
CONFIG = cp.get("general", "config_tisbackup").split(",")
SECTIONS = cp.get("general", "sections")
ADMIN_EMAIL = cp.get("general", "ADMIN_EMAIL")
BASE_DIR = cp.get("general", "base_config_dir")
tisbackup_config_file= CONFIG[0]
config_number=0
tisbackup_config_file = CONFIG[0]
config_number = 0
cp = ConfigParser()
cp.read(tisbackup_config_file)
backup_base_dir = cp.get('global','backup_base_dir')
dbstat = BackupStat(os.path.join(backup_base_dir,'log','tisbackup.sqlite'))
backup_base_dir = cp.get("global", "backup_base_dir")
dbstat = BackupStat(os.path.join(backup_base_dir, "log", "tisbackup.sqlite"))
mindate = None
error = None
info = None
app = Flask(__name__)
app.secret_key = 'fsiqefiuqsefARZ4Zfesfe34234dfzefzfe'
app.config['PROPAGATE_EXCEPTIONS'] = True
app.secret_key = "fsiqefiuqsefARZ4Zfesfe34234dfzefzfe"
app.config["PROPAGATE_EXCEPTIONS"] = True
tasks_db = os.path.join(tisbackup_root_dir,"tasks.sqlite")
tasks_db = os.path.join(tisbackup_root_dir, "tasks.sqlite")
def read_all_configs(base_dir):
raw_configs = []
list_config = []
config_base_dir = base_dir
# config_base_dir = base_dir
for file in os.listdir(base_dir):
if isfile(join(base_dir,file)):
raw_configs.append(join(base_dir,file))
if isfile(join(base_dir, file)):
raw_configs.append(join(base_dir, file))
for elem in raw_configs:
line = open(elem).readline()
if 'global' in line:
if "global" in line:
list_config.append(elem)
backup_dict = {}
backup_dict['rsync_ssh_list'] = []
backup_dict['rsync_btrfs_list'] = []
backup_dict['rsync_list'] = []
backup_dict['null_list'] = []
backup_dict['pgsql_list'] = []
backup_dict['mysql_list'] = []
#backup_dict['sqlserver_list'] = []
backup_dict['xva_list'] = []
backup_dict['metadata_list'] = []
#backup_dict['switch_list'] = []
#backup_dict['oracle_list'] = []
backup_dict["rsync_ssh_list"] = []
backup_dict["rsync_btrfs_list"] = []
backup_dict["rsync_list"] = []
backup_dict["null_list"] = []
backup_dict["pgsql_list"] = []
backup_dict["mysql_list"] = []
# backup_dict['sqlserver_list'] = []
backup_dict["xva_list"] = []
backup_dict["metadata_list"] = []
# backup_dict['switch_list'] = []
# backup_dict['oracle_list'] = []
result = []
cp = ConfigParser()
for config_file in list_config:
cp.read(config_file)
backup_base_dir = cp.get('global', 'backup_base_dir')
backup_base_dir = cp.get("global", "backup_base_dir")
backup = tis_backup(backup_base_dir=backup_base_dir)
backup.read_ini_file(config_file)
@ -110,11 +110,12 @@ def read_all_configs(base_dir):
backup_sections = all_sections
else:
for b in backup_sections:
if not b in all_sections:
raise Exception('Section %s is not defined in config file' % b)
if b not in all_sections:
raise Exception("Section %s is not defined in config file" % b)
if not backup_sections:
sections = [backup_item.backup_name for backup_item in backup.backup_list]
# never used..
# if not backup_sections:
# sections = [backup_item.backup_name for backup_item in backup.backup_list]
for backup_item in backup.backup_list:
if backup_item.backup_name in backup_sections:
@ -125,35 +126,28 @@ def read_all_configs(base_dir):
result.append(b)
for row in result:
backup_name = row['backup_name']
server_name = row['server_name']
backup_type = row['type']
backup_name = row["backup_name"]
server_name = row["server_name"]
backup_type = row["type"]
if backup_type == "xcp-dump-metadata":
backup_dict['metadata_list'].append(
[server_name, backup_name, backup_type, ""])
backup_dict["metadata_list"].append([server_name, backup_name, backup_type, ""])
if backup_type == "rsync+ssh":
remote_dir = row['remote_dir']
backup_dict['rsync_ssh_list'].append(
[server_name, backup_name, backup_type, remote_dir])
remote_dir = row["remote_dir"]
backup_dict["rsync_ssh_list"].append([server_name, backup_name, backup_type, remote_dir])
if backup_type == "rsync+btrfs+ssh":
remote_dir = row['remote_dir']
backup_dict['rsync_btrfs_list'].append(
[server_name, backup_name, backup_type, remote_dir])
remote_dir = row["remote_dir"]
backup_dict["rsync_btrfs_list"].append([server_name, backup_name, backup_type, remote_dir])
if backup_type == "rsync":
remote_dir = row['remote_dir']
backup_dict['rsync_list'].append(
[server_name, backup_name, backup_type, remote_dir])
remote_dir = row["remote_dir"]
backup_dict["rsync_list"].append([server_name, backup_name, backup_type, remote_dir])
if backup_type == "null":
backup_dict['null_list'].append(
[server_name, backup_name, backup_type, ""])
backup_dict["null_list"].append([server_name, backup_name, backup_type, ""])
if backup_type == "pgsql+ssh":
db_name = row['db_name'] if len(row['db_name']) > 0 else '*'
backup_dict['pgsql_list'].append(
[server_name, backup_name, backup_type, db_name])
db_name = row["db_name"] if len(row["db_name"]) > 0 else "*"
backup_dict["pgsql_list"].append([server_name, backup_name, backup_type, db_name])
if backup_type == "mysql+ssh":
db_name = row['db_name'] if len(row['db_name']) > 0 else '*'
backup_dict['mysql_list'].append(
[server_name, backup_name, backup_type, db_name])
db_name = row["db_name"] if len(row["db_name"]) > 0 else "*"
backup_dict["mysql_list"].append([server_name, backup_name, backup_type, db_name])
# if backup_type == "sqlserver+ssh":
# db_name = row['db_name']
# backup_dict['sqlserver_list'].append(
@ -163,12 +157,11 @@ def read_all_configs(base_dir):
# backup_dict['oracle_list'].append(
# [server_name, backup_name, backup_type, db_name])
if backup_type == "xen-xva":
backup_dict['xva_list'].append(
[server_name, backup_name, backup_type, ""])
backup_dict["xva_list"].append([server_name, backup_name, backup_type, ""])
# if backup_type == "switch":
# backup_dict['switch_list'].append(
# [server_name, backup_name, backup_type, ""])
return backup_dict
@ -177,7 +170,7 @@ def read_config():
cp = ConfigParser()
cp.read(config_file)
backup_base_dir = cp.get('global','backup_base_dir')
backup_base_dir = cp.get("global", "backup_base_dir")
backup = tis_backup(backup_base_dir=backup_base_dir)
backup.read_ini_file(config_file)
@ -188,56 +181,58 @@ def read_config():
backup_sections = all_sections
else:
for b in backup_sections:
if not b in all_sections:
raise Exception('Section %s is not defined in config file' % b)
if b not in all_sections:
raise Exception("Section %s is not defined in config file" % b)
result = []
if not backup_sections:
sections = [backup_item.backup_name for backup_item in backup.backup_list]
# not used ...
# if not backup_sections:
# sections = [backup_item.backup_name for backup_item in backup.backup_list]
for backup_item in backup.backup_list:
if backup_item.backup_name in backup_sections:
b = {}
for attrib_name in backup_item.required_params+backup_item.optional_params:
if hasattr(backup_item,attrib_name):
b[attrib_name] = getattr(backup_item,attrib_name)
for attrib_name in backup_item.required_params + backup_item.optional_params:
if hasattr(backup_item, attrib_name):
b[attrib_name] = getattr(backup_item, attrib_name)
result.append(b)
backup_dict = {}
backup_dict['rsync_ssh_list'] = []
backup_dict['rsync_btrfs_list'] = []
backup_dict['rsync_list'] = []
backup_dict['null_list'] = []
backup_dict['pgsql_list'] = []
backup_dict['mysql_list'] = []
#backup_dict['sqlserver_list'] = []
backup_dict['xva_list'] = []
backup_dict['metadata_list'] = []
#backup_dict['switch_list'] = []
#backup_dict['oracle_list'] = []
backup_dict["rsync_ssh_list"] = []
backup_dict["rsync_btrfs_list"] = []
backup_dict["rsync_list"] = []
backup_dict["null_list"] = []
backup_dict["pgsql_list"] = []
backup_dict["mysql_list"] = []
# backup_dict['sqlserver_list'] = []
backup_dict["xva_list"] = []
backup_dict["metadata_list"] = []
# backup_dict['switch_list'] = []
# backup_dict['oracle_list'] = []
for row in result:
backup_name = row['backup_name']
server_name = row['server_name']
backup_type = row['type']
backup_name = row["backup_name"]
server_name = row["server_name"]
backup_type = row["type"]
if backup_type == "xcp-dump-metadata":
backup_dict['metadata_list'].append([server_name, backup_name, backup_type, ""])
backup_dict["metadata_list"].append([server_name, backup_name, backup_type, ""])
if backup_type == "rsync+ssh":
remote_dir = row['remote_dir']
backup_dict['rsync_ssh_list'].append([server_name, backup_name, backup_type,remote_dir])
remote_dir = row["remote_dir"]
backup_dict["rsync_ssh_list"].append([server_name, backup_name, backup_type, remote_dir])
if backup_type == "rsync+btrfs+ssh":
remote_dir = row['remote_dir']
backup_dict['rsync_btrfs_list'].append([server_name, backup_name, backup_type,remote_dir])
remote_dir = row["remote_dir"]
backup_dict["rsync_btrfs_list"].append([server_name, backup_name, backup_type, remote_dir])
if backup_type == "rsync":
remote_dir = row['remote_dir']
backup_dict['rsync_list'].append([server_name, backup_name, backup_type,remote_dir])
remote_dir = row["remote_dir"]
backup_dict["rsync_list"].append([server_name, backup_name, backup_type, remote_dir])
if backup_type == "null":
backup_dict['null_list'].append([server_name, backup_name, backup_type, ""])
backup_dict["null_list"].append([server_name, backup_name, backup_type, ""])
if backup_type == "pgsql+ssh":
db_name = row['db_name'] if len(row['db_name']) > 0 else '*'
backup_dict['pgsql_list'].append([server_name, backup_name, backup_type, db_name])
db_name = row["db_name"] if len(row["db_name"]) > 0 else "*"
backup_dict["pgsql_list"].append([server_name, backup_name, backup_type, db_name])
if backup_type == "mysql+ssh":
db_name = row['db_name'] if len(row['db_name']) > 0 else '*'
backup_dict['mysql_list'].append([server_name, backup_name, backup_type, db_name])
db_name = row["db_name"] if len(row["db_name"]) > 0 else "*"
backup_dict["mysql_list"].append([server_name, backup_name, backup_type, db_name])
# if backup_type == "sqlserver+ssh":
# db_name = row['db_name']
# backup_dict['sqlserver_list'].append([server_name, backup_name, backup_type, db_name])
@ -245,49 +240,68 @@ def read_config():
# db_name = row['db_name']
# backup_dict['oracle_list'].append([server_name, backup_name, backup_type, db_name])
if backup_type == "xen-xva":
backup_dict['xva_list'].append([server_name, backup_name, backup_type, ""])
backup_dict["xva_list"].append([server_name, backup_name, backup_type, ""])
# if backup_type == "switch":
# backup_dict['switch_list'].append([server_name, backup_name, backup_type, ""])
return backup_dict
@app.route('/')
@app.route("/")
def backup_all():
backup_dict = read_config()
return render_template('backups.html', backup_list = backup_dict)
return render_template("backups.html", backup_list=backup_dict)
@app.route('/config_number/')
@app.route('/config_number/<int:id>')
@app.route("/config_number/")
@app.route("/config_number/<int:id>")
def set_config_number(id=None):
if id != None and len(CONFIG) > id:
if id is not None and len(CONFIG) > id:
global config_number
config_number=id
config_number = id
read_config()
return jsonify(configs=CONFIG,config_number=config_number)
return jsonify(configs=CONFIG, config_number=config_number)
@app.route('/all_json')
@app.route("/all_json")
def backup_all_json():
backup_dict = read_all_configs(BASE_DIR)
return json.dumps(backup_dict['rsync_list']+backup_dict['rsync_btrfs_list']+backup_dict['rsync_ssh_list']+backup_dict['pgsql_list']+backup_dict['mysql_list']+backup_dict['xva_list']+backup_dict['null_list']+backup_dict['metadata_list'])
#+ backup_dict['switch_list'])+backup_dict['sqlserver_list']
return json.dumps(
backup_dict["rsync_list"]
+ backup_dict["rsync_btrfs_list"]
+ backup_dict["rsync_ssh_list"]
+ backup_dict["pgsql_list"]
+ backup_dict["mysql_list"]
+ backup_dict["xva_list"]
+ backup_dict["null_list"]
+ backup_dict["metadata_list"]
)
# + backup_dict['switch_list'])+backup_dict['sqlserver_list']
@app.route('/json')
@app.route("/json")
def backup_json():
backup_dict = read_config()
return json.dumps(backup_dict['rsync_list']+backup_dict['rsync_btrfs_list']+backup_dict['rsync_ssh_list']+backup_dict['pgsql_list']+backup_dict['mysql_list']+backup_dict['xva_list']+backup_dict['null_list']+backup_dict['metadata_list'])
#+ backup_dict['switch_list'])+backup_dict['sqlserver_list']
return json.dumps(
backup_dict["rsync_list"]
+ backup_dict["rsync_btrfs_list"]
+ backup_dict["rsync_ssh_list"]
+ backup_dict["pgsql_list"]
+ backup_dict["mysql_list"]
+ backup_dict["xva_list"]
+ backup_dict["null_list"]
+ backup_dict["metadata_list"]
)
# + backup_dict['switch_list'])+backup_dict['sqlserver_list']
def check_usb_disk():
"""This method returns the mounts point of FIRST external disk"""
# disk_name = []
# disk_name = []
usb_disk_list = []
for name in glob.glob('/dev/sd[a-z]'):
for name in glob.glob("/dev/sd[a-z]"):
for line in os.popen("udevadm info -q env -n %s" % name):
if re.match("ID_PATH=.*usb.*", line):
usb_disk_list += [ name ]
usb_disk_list += [name]
if len(usb_disk_list) == 0:
raise_error("Cannot find any external usb disk", "You should plug the usb hard drive into the server")
@ -296,20 +310,23 @@ def check_usb_disk():
usb_partition_list = []
for usb_disk in usb_disk_list:
cmd = "udevadm info -q path -n %s" % usb_disk + '1'
output = os.popen(cmd).read()
cmd = "udevadm info -q path -n %s" % usb_disk + "1"
output = os.popen(cmd).read()
print("cmd : " + cmd)
print("output : " + output)
if '/devices/pci' in output:
#flash("partition found: %s1" % usb_disk)
if "/devices/pci" in output:
# flash("partition found: %s1" % usb_disk)
usb_partition_list.append(usb_disk + "1")
print(usb_partition_list)
if len(usb_partition_list) ==0:
raise_error("The drive %s has no partition" % (usb_disk_list[0] ), "You should initialize the usb drive and format an ext4 partition with TISBACKUP label")
return ""
if len(usb_partition_list) == 0:
raise_error(
"The drive %s has no partition" % (usb_disk_list[0]),
"You should initialize the usb drive and format an ext4 partition with TISBACKUP label",
)
return ""
tisbackup_partition_list = []
for usb_partition in usb_partition_list:
@ -317,133 +334,139 @@ def check_usb_disk():
flash("tisbackup backup partition found: %s" % usb_partition)
tisbackup_partition_list.append(usb_partition)
print(tisbackup_partition_list)
print(tisbackup_partition_list)
if len(tisbackup_partition_list) ==0:
raise_error("No tisbackup partition exist on disk %s" % (usb_disk_list[0] ), "You should initialize the usb drive and format an ext4 partition with TISBACKUP label")
if len(tisbackup_partition_list) == 0:
raise_error(
"No tisbackup partition exist on disk %s" % (usb_disk_list[0]),
"You should initialize the usb drive and format an ext4 partition with TISBACKUP label",
)
return ""
if len(tisbackup_partition_list) > 1:
if len(tisbackup_partition_list) > 1:
raise_error("There are many usb disk", "You should plug remove one of them")
return ""
return tisbackup_partition_list[0]
def check_already_mount(partition_name,refresh):
with open('/proc/mounts') as f:
def check_already_mount(partition_name, refresh):
with open("/proc/mounts") as f:
mount_point = ""
for line in f.readlines():
if line.startswith(partition_name):
mount_point = line.split(' ')[1]
mount_point = line.split(" ")[1]
if not refresh:
run_command("/bin/umount %s" % mount_point)
os.rmdir(mount_point)
os.rmdir(mount_point)
return mount_point
def run_command(cmd, info=""):
flash("Executing: %s"% cmd)
from subprocess import CalledProcessError, check_output
result =""
flash("Executing: %s" % cmd)
from subprocess import CalledProcessError, check_output
result = ""
try:
result = check_output(cmd, stderr=subprocess.STDOUT,shell=True)
except CalledProcessError as e:
raise_error(result,info)
result = check_output(cmd, stderr=subprocess.STDOUT, shell=True)
except CalledProcessError:
raise_error(result, info)
return result
def check_mount_disk(partition_name, refresh):
mount_point = check_already_mount(partition_name, refresh)
if not refresh:
mount_point = "/mnt/TISBACKUP-" +str(time.time())
def check_mount_disk(partition_name, refresh):
mount_point = check_already_mount(partition_name, refresh)
if not refresh:
mount_point = "/mnt/TISBACKUP-" + str(time.time())
os.mkdir(mount_point)
flash("must mount " + partition_name )
flash("must mount " + partition_name)
cmd = "mount %s %s" % (partition_name, mount_point)
if run_command(cmd,"You should manualy mount the usb drive") != "":
if run_command(cmd, "You should manualy mount the usb drive") != "":
flash("Remove directory: %s" % mount_point)
os.rmdir(mount_point)
return ""
os.rmdir(mount_point)
return ""
return mount_point
@app.route('/status.json')
@app.route("/status.json")
def export_backup_status():
exports = dbstat.query('select * from stats where TYPE="EXPORT" and backup_start>="%s"' % mindate)
error = ""
finish=not runnings_backups()
if get_task() != None and finish:
finish = not runnings_backups()
if get_task() is not None and finish:
status = get_task().get()
if status != "ok":
error = "Export failing with error: "+status
error = "Export failing with error: " + status
return jsonify(data=exports, finish=finish, error=error)
return jsonify(data=exports,finish=finish,error=error)
def runnings_backups():
task = get_task()
is_runnig = (task != None)
finish = ( is_runnig and task.get() != None)
task = get_task()
is_runnig = task is not None
finish = is_runnig and task.get() is not None
return is_runnig and not finish
@app.route('/backups.json')
@app.route("/backups.json")
def last_backup_json():
exports = dbstat.query('select * from stats where TYPE="BACKUP" ORDER BY backup_start DESC ')
return Response(response=json.dumps(exports),
status=200,
mimetype="application/json")
return Response(response=json.dumps(exports), status=200, mimetype="application/json")
@app.route('/last_backups')
@app.route("/last_backups")
def last_backup():
exports = dbstat.query('select * from stats where TYPE="BACKUP" ORDER BY backup_start DESC LIMIT 20 ')
return render_template("last_backups.html", backups=exports)
@app.route('/export_backup')
@app.route("/export_backup")
def export_backup():
raise_error("", "")
backup_dict = read_config()
sections = []
backup_sections = []
for backup_types in backup_dict:
for backup_types in backup_dict:
if backup_types == "null_list":
continue
for section in backup_dict[backup_types]:
#if section.count > 0:
# if section.count > 0:
if len(section) > 0:
sections.append(section[1])
noJobs = (not runnings_backups())
noJobs = not runnings_backups()
if "start" in list(request.args.keys()) or not noJobs:
start=True
start = True
if "sections" in list(request.args.keys()):
backup_sections = request.args.getlist('sections')
backup_sections = request.args.getlist("sections")
else:
start=False
start = False
cp.read(tisbackup_config_file)
partition_name = check_usb_disk()
if partition_name:
if noJobs:
mount_point = check_mount_disk( partition_name, False)
mount_point = check_mount_disk(partition_name, False)
else:
mount_point = check_mount_disk( partition_name, True)
mount_point = check_mount_disk(partition_name, True)
if noJobs:
global mindate
mindate = datetime2isodate(datetime.datetime.now())
global mindate
mindate = datetime2isodate(datetime.datetime.now())
if not error and start:
print(tisbackup_config_file)
task = run_export_backup(base=backup_base_dir, config_file=CONFIG[config_number], mount_point=mount_point, backup_sections=",".join([str(x) for x in backup_sections]))
task = run_export_backup(
base=backup_base_dir,
config_file=CONFIG[config_number],
mount_point=mount_point,
backup_sections=",".join([str(x) for x in backup_sections]),
)
set_task(task)
return render_template("export_backup.html", error=error, start=start, info=info, email=ADMIN_EMAIL, sections=sections)
@ -453,9 +476,10 @@ def raise_error(strError, strInfo):
info = strInfo
if __name__ == "__main__":
if __name__ == "__main__":
read_config()
from os import environ
if 'WINGDB_ACTIVE' in environ:
if "WINGDB_ACTIVE" in environ:
app.debug = False
app.run(host= '0.0.0.0',port=8080)
app.run(host="0.0.0.0", port=8080)