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' python-version: '3.12'
cache: 'pip' # caching pip dependencies cache: 'pip' # caching pip dependencies
- run: pip install ruff - run: pip install ruff
- run: | - run: |
ruff check . ruff check .
ruff fix .
# - uses: stefanzweifel/git-auto-commit-action@v4 # - uses: stefanzweifel/git-auto-commit-action@v4
# with: # with:
# commit_message: 'style fixes by ruff' # commit_message: 'style fixes by ruff'

2
.gitignore vendored
View File

@ -2,11 +2,13 @@
*.swp *.swp
*~ *~
*.pyc *.pyc
__pycache__/*
/tasks.sqlite /tasks.sqlite
/tasks.sqlite-wal /tasks.sqlite-wal
/srvinstallation /srvinstallation
/tasks.sqlite-shm /tasks.sqlite-shm
.idea .idea
.ruff_cache/*
/deb/builddir /deb/builddir
/deb/*.deb /deb/*.deb
/lib /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" "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 import os
from huey.backends.sqlite_backend import SqliteQueue,SqliteDataStore import sys
from huey.api import Huey, create_task
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__))) tisbackup_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__)))
tasks_db = os.path.join(tisbackup_root_dir,"tasks.sqlite") tasks_db = os.path.join(tisbackup_root_dir, "tasks.sqlite")
queue = SqliteQueue('tisbackups',tasks_db) huey = SqlHuey(name="tisbackups",filename=tasks_db,always_eager=False,storage_class=SqliteStorage)
result_store = SqliteDataStore('tisbackups',tasks_db)
huey = Huey(queue,result_store,always_eager=False)

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 ### 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 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. 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```. 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 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 cd /etc/tis
cp tisbackup-config.ini.sample tisbackup-config.ini cp tisbackup-config.ini.sample tisbackup-config.ini
vi 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 cd
ssh-keygen -t rsa -b 2048 ssh-keygen -t rsa -b 2048
``` ```
(press enter for each step) (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@machine1
ssh-copy-id -i /root/.ssh/id_rsa.pub root@machine2 ssh-copy-id -i /root/.ssh/id_rsa.pub root@machine2
``` ```
etc. etc.
Eventually modify ```/etc/cron.d/tisbackup``` for your needs. Eventually modify ```/etc/cron.d/tisbackup``` for your needs.
Finalize the installation with: Finalize the installation with:
``` ```
tisbackup -d backup tisbackup -d backup
systemctl start tisbackup_gui 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 dpkg --force-all --purge tis-tisbackup
apt autoremove apt autoremove
``` ```

View File

@ -3,8 +3,7 @@ Version: 1-__VERSION__
Section: base Section: base
Priority: optional Priority: optional
Architecture: all 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> Maintainer: Tranquil-IT <technique@tranquil.it>
Description: TISBackup backup management Description: TISBackup backup management
Homepage: https://www.tranquil.it Homepage: https://www.tranquil.it

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/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') 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}" VERSION="${VERSION_SHORT}.${GIT_COUNT}-deb${VERSION_DEB}"
rm -f *.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 chmod 755 ./builddir/opt/tisbackup/tisbackup.py
dpkg-deb --build builddir tis-tisbackup-1-${VERSION}.deb dpkg-deb --build builddir tis-tisbackup-1-${VERSION}.deb

View File

@ -765,4 +765,4 @@ div.math:hover a.headerlink {
#top-link { #top-link {
display: none; 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, HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt', SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false 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 * @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 * @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"]; 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 * Porter Stemmer
*/ */
@ -293,5 +293,3 @@ function splitQuery(query) {
} }
return result; 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 .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ .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 content="Documentation, TISBackup, configuration, backup jobs" name="keywords" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.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/custom.css" type="text/css" />
<link rel="stylesheet" href="_static/css/ribbon.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="stylesheet" href="_static/theme_overrides.css" type="text/css" />
<link rel="shortcut icon" href="_static/favicon.ico"/> <link rel="shortcut icon" href="_static/favicon.ico"/>
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script> <script src="_static/js/html5shiv.min.js"></script>
<![endif]--> <![endif]-->
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script> <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/jquery.js"></script>
<script src="_static/underscore.js"></script> <script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script> <script src="_static/doctools.js"></script>
<script src="_static/language_data.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/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" /> <link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" /> <link rel="search" title="Search" href="search.html" />
<link rel="next" title="Using TISBackup" href="using_tisbackup.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> </head>
<body class="wy-body-for-nav"> <body class="wy-body-for-nav">
<div class="wy-grid-for-nav"> <div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side"> <nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll"> <div class="wy-side-scroll">
<div class="wy-side-nav-search" > <div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> TISBackup <a href="index.html" class="icon icon-home"> TISBackup
</a> </a>
<div class="version"> <div class="version">
1.8 1.8
</div> </div>
<div role="search"> <div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get"> <form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" /> <input type="text" name="q" placeholder="Search docs" />
@ -86,17 +86,17 @@
</form> </form>
</div> </div>
</div> </div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation"> <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> <p><span class="caption-text">Presenting TISBackup</span></p>
<ul class="current"> <ul class="current">
<li class="toctree-l1"><a class="reference internal" href="presenting_tisbackup.html">Technical background for TISBackup</a></li> <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> <li class="toctree-l1"><a class="reference internal" href="screenshots.html">Screenshots of TISBackup</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"> <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation"> <nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i> <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">TISBackup</a> <a href="index.html">TISBackup</a>
</nav> </nav>
<div class="wy-nav-content"> <div class="wy-nav-content">
<div class="rst-content"> <div class="rst-content">
@ -168,28 +168,28 @@
<div role="navigation" aria-label="breadcrumbs navigation"> <div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs"> <ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li> <li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Configuring the backup jobs</li> <li>Configuring the backup jobs</li>
<li class="wy-breadcrumbs-aside"> <li class="wy-breadcrumbs-aside">
<a href="_sources/configuring_tisbackup.rst.txt" rel="nofollow"> View page source</a> <a href="_sources/configuring_tisbackup.rst.txt" rel="nofollow"> View page source</a>
</li> </li>
</ul> </ul>
<hr/> <hr/>
</div> </div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article"> <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody"> <div itemprop="articleBody">
<section id="configuring-the-backup-jobs"> <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> <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, <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>
</div> </div>
<footer> <footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation"> <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
@ -456,14 +456,14 @@ with read-write access only for it.</p>
</p> </p>
</div> </div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</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> </footer>
</div> </div>
@ -472,7 +472,7 @@ with read-write access only for it.</p>
</section> </section>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
jQuery(function () { jQuery(function () {
@ -480,11 +480,11 @@ with read-write access only for it.</p>
}); });
</script> </script>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=UA-89790248-2"></script>
@ -499,4 +499,4 @@ gtag('config', 'UA-89790248-2');
</body> </body>
</html> </html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@
# #
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
import sys import sys
try: try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko import paramiko
@ -27,15 +28,17 @@ except ImportError as e:
sys.stderr = sys.__stderr__ sys.stderr = sys.__stderr__
import datetime
import base64 import base64
import datetime
import os import os
from libtisbackup.common import *
import re import re
from libtisbackup.common import *
class backup_oracle(backup_generic): class backup_oracle(backup_generic):
"""Backup a oracle database as zipped file through ssh""" """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'] required_params = backup_generic.required_params + ['db_name','private_key', 'userid']
optional_params = ['username', 'remote_backup_dir', 'ignore_error_oracle_code'] optional_params = ['username', 'remote_backup_dir', 'ignore_error_oracle_code']
db_name='' db_name=''
@ -44,7 +47,7 @@ class backup_oracle(backup_generic):
ignore_error_oracle_code = [ ] ignore_error_oracle_code = [ ]
def do_backup(self,stats): 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) 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: try:
mykey = paramiko.RSAKey.from_private_key_file(self.private_key) 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 = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 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() t = datetime.datetime.now()
self.backup_start_date = t.strftime('%Y%m%d-%Hh%Mm%S') 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' dumpfile= self.remote_backup_dir + '/' + self.db_name + '_' + self.backup_start_date+'.dmp'
@ -68,10 +71,10 @@ class backup_oracle(backup_generic):
else: else:
print(('mkdir "%s"' % self.dest_dir)) print(('mkdir "%s"' % self.dest_dir))
else: 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 # dump db
stats['status']='Dumping' 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) self.logger.debug('[%s] Dump DB : %s',self.backup_name,cmd)
if not self.dry_run: if not self.dry_run:
(error_code,output) = ssh_exec(cmd,ssh=self.ssh) (error_code,output) = ssh_exec(cmd,ssh=self.ssh)
@ -94,7 +97,7 @@ class backup_oracle(backup_generic):
# zip the file # zip the file
stats['status']='Zipping' stats['status']='Zipping'
cmd = 'gzip %s' % dumpfile cmd = 'gzip %s' % dumpfile
self.logger.debug('[%s] Compress backup : %s',self.backup_name,cmd) self.logger.debug('[%s] Compress backup : %s',self.backup_name,cmd)
if not self.dry_run: if not self.dry_run:
(error_code,output) = ssh_exec(cmd,ssh=self.ssh) (error_code,output) = ssh_exec(cmd,ssh=self.ssh)
@ -117,7 +120,7 @@ class backup_oracle(backup_generic):
stats['total_files_count']=1 stats['total_files_count']=1
stats['written_files_count']=1 stats['written_files_count']=1
stats['total_bytes']=os.stat(localpath).st_size 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['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['backup_location'] = self.dest_dir
stats['status']='RMTemp' stats['status']='RMTemp'
@ -131,7 +134,7 @@ class backup_oracle(backup_generic):
filelist = os.listdir(self.backup_dir) filelist = os.listdir(self.backup_dir)
filelist.sort() 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: for item in filelist:
if p.match(item): if p.match(item):
dir_name = os.path.join(self.backup_dir,item) dir_name = os.path.join(self.backup_dir,item)

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@
import sys import sys
try: try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko import paramiko
@ -32,6 +33,7 @@ sys.stderr = sys.__stderr__
from .common import * from .common import *
class backup_samba4(backup_generic): class backup_samba4(backup_generic):
"""Backup a samba4 databases as gzipped tdbs file through ssh""" """Backup a samba4 databases as gzipped tdbs file through ssh"""
type = 'samba4' type = 'samba4'
@ -163,4 +165,4 @@ class backup_samba4(backup_generic):
self.logger.info('Skipping %s, already registered',dir_name) 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 import sys
try: try:
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko import paramiko
@ -30,11 +31,13 @@ except ImportError as e:
sys.stderr = sys.__stderr__ sys.stderr = sys.__stderr__
import datetime
import base64 import base64
import datetime
import os import os
from .common import * from .common import *
class backup_sqlserver(backup_generic): class backup_sqlserver(backup_generic):
"""Backup a SQLSERVER database as gzipped sql file through ssh""" """Backup a SQLSERVER database as gzipped sql file through ssh"""
type = 'sqlserver+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 base64
import datetime
import logging
import os
import os.path
import re
import select
import socket import socket
import requests import time
import pexpect import urllib.error
import urllib.parse
import urllib.request
from stat import * from stat import *
import pexpect
import requests
from . import XenAPI
from .common import *
class backup_switch(backup_generic): class backup_switch(backup_generic):
"""Backup a startup-config on a switch""" """Backup a startup-config on a switch"""
@ -149,7 +152,7 @@ class backup_switch(backup_generic):
else: else:
child.sendline(self.switch_user) child.sendline(self.switch_user)
child.expect(".*#") child.expect(".*#")
child.sendline( "terminal datadump") child.sendline( "terminal datadump")
child.expect("#") child.expect("#")
child.sendline( "show startup-config") child.sendline( "show startup-config")
@ -259,4 +262,3 @@ if __name__=='__main__':
cp.read('/opt/tisbackup/configtest.ini') cp.read('/opt/tisbackup/configtest.ini')
b = backup_xva() b = backup_xva()
b.read_config(cp) 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 atexit
import getpass import getpass
from datetime import date, datetime, timedelta
import pyVmomi
import requests import requests
from pyVim.connect import Disconnect, SmartConnect
from pyVmomi import vim, vmodl
# Disable HTTPS verification warnings. # Disable HTTPS verification warnings.
from requests.packages import urllib3 from requests.packages import urllib3
from .common import *
urllib3.disable_warnings() urllib3.disable_warnings()
import os import os
import time import re
import tarfile import tarfile
import re import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from stat import * from stat import *
@ -59,15 +59,15 @@ class backup_vmdk(backup_generic):
cookie_text = " " + cookie_value + "; $" + cookie_path cookie_text = " " + cookie_value + "; $" + cookie_path
# Make a cookie # Make a cookie
cookie = dict() cookie = dict()
cookie[cookie_name] = cookie_text cookie[cookie_name] = cookie_text
return cookie return cookie
def download_file(self,url, local_filename, cookie, headers): def download_file(self,url, local_filename, cookie, headers):
r = requests.get(url, stream=True, headers=headers,cookies=cookie,verify=False) r = requests.get(url, stream=True, headers=headers,cookies=cookie,verify=False)
with open(local_filename, 'wb') as f: with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024*1024*64): for chunk in r.iter_content(chunk_size=1024*1024*64):
if chunk: if chunk:
f.write(chunk) f.write(chunk)
f.flush() f.flush()
return local_filename return local_filename
@ -78,7 +78,7 @@ class backup_vmdk(backup_generic):
try: try:
infos = HttpNfcLease.info infos = HttpNfcLease.info
device_urls = infos.deviceUrl device_urls = infos.deviceUrl
vmdks = [] vmdks = []
for device_url in device_urls: for device_url in device_urls:
deviceId = device_url.key deviceId = device_url.key
deviceUrlStr = device_url.url deviceUrlStr = device_url.url
@ -89,7 +89,7 @@ class backup_vmdk(backup_generic):
cookie = self.make_compatible_cookie(si._stub.cookie) cookie = self.make_compatible_cookie(si._stub.cookie)
headers = {'Content-Type': 'application/octet-stream'} headers = {'Content-Type': 'application/octet-stream'}
self.logger.debug("[%s] exporting disk: %s" %(self.server_name,diskFileName)) self.logger.debug("[%s] exporting disk: %s" %(self.server_name,diskFileName))
self.download_file(diskUrlStr, diskFileName, cookie, headers) self.download_file(diskUrlStr, diskFileName, cookie, headers)
vmdks.append({"filename":diskFileName,"id":deviceId}) vmdks.append({"filename":diskFileName,"id":deviceId})
finally: finally:
@ -99,8 +99,8 @@ class backup_vmdk(backup_generic):
def create_ovf(self,vm,vmdks): def create_ovf(self,vm,vmdks):
ovfDescParams = vim.OvfManager.CreateDescriptorParams() ovfDescParams = vim.OvfManager.CreateDescriptorParams()
ovf = si.content.ovfManager.CreateDescriptor(vm, ovfDescParams) ovf = si.content.ovfManager.CreateDescriptor(vm, ovfDescParams)
root = ET.fromstring(ovf.ovfDescriptor) root = ET.fromstring(ovf.ovfDescriptor)
new_id = list(root[0][1].attrib.values())[0][1:3] new_id = list(root[0][1].attrib.values())[0][1:3]
ovfFiles = [] ovfFiles = []
for vmdk in vmdks: 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)) ovfFiles.append(vim.OvfManager.OvfFile(size=os.path.getsize(vmdk['filename']), path=vmdk['filename'], deviceId=id))
ovfDescParams = vim.OvfManager.CreateDescriptorParams() 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" ovf_filename = vm.name+".ovf"
self.logger.debug("[%s] creating ovf file: %s" %(self.server_name,ovf_filename)) self.logger.debug("[%s] creating ovf file: %s" %(self.server_name,ovf_filename))
with open(ovf_filename, "w") as f: with open(ovf_filename, "w") as f:
f.write(ovf.ovfDescriptor) f.write(ovf.ovfDescriptor)
return ovf_filename return ovf_filename
def create_ova(self,vm, vmdks, ovf_filename): def create_ova(self,vm, vmdks, ovf_filename):
@ -131,9 +131,9 @@ class backup_vmdk(backup_generic):
def clone_vm(self,vm): 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)) 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 prefix_vmclone = self.prefix_clone
clone_name = prefix_vmclone + vm.name clone_name = prefix_vmclone + vm.name
datastore = '[%s]' % vm.datastore[0].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) config = vim.vm.ConfigSpec(name=clone_name, memoryMB=vm.summary.config.memorySizeMB, numCPUs=vm.summary.config.numCpu, files=vmx_file)
hosts = datacenter.hostFolder.childEntity 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] new_vm = [x for x in vmFolder.childEntity if x.name == clone_name][0]
controller = vim.vm.device.VirtualDeviceSpec() controller = vim.vm.device.VirtualDeviceSpec()
controller.operation = vim.vm.device.VirtualDeviceSpec.Operation.add controller.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
controller.device = vim.vm.device.VirtualLsiLogicController(busNumber=0,sharedBus='noSharing') controller.device = vim.vm.device.VirtualLsiLogicController(busNumber=0,sharedBus='noSharing')
controller.device.key = 0 controller.device.key = 0
i=0 i=0
vm_devices = [] vm_devices = []
@ -189,7 +189,7 @@ class backup_vmdk(backup_generic):
vm_devices.append(controller) vm_devices.append(controller)
config.deviceChange=vm_devices config.deviceChange=vm_devices
self.wait_task(new_vm.ReconfigVM_Task(config)) self.wait_task(new_vm.ReconfigVM_Task(config))
@ -198,7 +198,7 @@ class backup_vmdk(backup_generic):
def wait_task(self,task): def wait_task(self,task):
while task.info.state in ["queued", "running"]: while task.info.state in ["queued", "running"]:
time.sleep(2) 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 return task
@ -213,7 +213,7 @@ class backup_vmdk(backup_generic):
else: else:
print('mkdir "%s"' % dest_dir) print('mkdir "%s"' % dest_dir)
else: 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) os.chdir(dest_dir)
user_esx, password_esx, null = open(self.password_file).read().split('\n') 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: if vm.name == self.server_name:
vm_is_off = vm.summary.runtime.powerState == "poweredOff" vm_is_off = vm.summary.runtime.powerState == "poweredOff"
if str2bool(self.halt_vm): if str2bool(self.halt_vm):
vm.ShutdownGuest() vm.ShutdownGuest()
vm_is_off = True vm_is_off = True
if vm_is_off: if vm_is_off:
vmdks = self.export_vmdks(vm) vmdks = self.export_vmdks(vm)
ovf_filename = self.create_ovf(vm, vmdks) ovf_filename = self.create_ovf(vm, vmdks)
else: else:
new_vm = self.clone_vm(vm) new_vm = self.clone_vm(vm)
vmdks = self.export_vmdks(new_vm) vmdks = self.export_vmdks(new_vm)
ovf_filename = self.create_ovf(vm, vmdks) 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): 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): if str2bool(self.halt_vm):
vm.PowerOnVM() vm.PowerOnVM()
if os.path.exists(dest_dir): 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['written_bytes'] += os.stat(file)[ST_SIZE]
stats['total_files_count'] += 1 stats['total_files_count'] += 1
stats['written_files_count'] += 1 stats['written_files_count'] += 1
@ -266,10 +266,10 @@ class backup_vmdk(backup_generic):
stats['written_bytes'] = 0 stats['written_bytes'] = 0
stats['backup_location'] = dest_dir stats['backup_location'] = dest_dir
stats['log']='XVA backup from %s OK, %d bytes written' % (self.server_name,stats['written_bytes']) 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: except BaseException as e:
stats['status']='ERROR' stats['status']='ERROR'
@ -279,4 +279,3 @@ class backup_vmdk(backup_generic):
register_driver(backup_vmdk) register_driver(backup_vmdk)

View File

@ -20,12 +20,14 @@
from .common import *
import paramiko import paramiko
from .common import *
class backup_xcp_metadata(backup_generic): class backup_xcp_metadata(backup_generic):
"""Backup metatdata of a xcp pool using xe pool-dump-database""" """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'] required_params = ['type','server_name','private_key','backup_name']
def do_backup(self,stats): def do_backup(self,stats):
@ -58,7 +60,7 @@ class backup_xcp_metadata(backup_generic):
stats['total_files_count']=1 stats['total_files_count']=1
stats['written_files_count']=1 stats['written_files_count']=1
stats['total_bytes']=os.stat(localpath).st_size 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['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['backup_location'] = localpath
stats['status']='OK' stats['status']='OK'
@ -72,7 +74,7 @@ class backup_xcp_metadata(backup_generic):
filelist = os.listdir(self.backup_dir) filelist = os.listdir(self.backup_dir)
filelist.sort() 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: for item in filelist:
sr = p.match(item) sr = p.match(item)
if sr: if sr:
@ -82,7 +84,7 @@ class backup_xcp_metadata(backup_generic):
self.logger.info('Registering %s from %s',file_name,fileisodate(file_name)) 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]) size_bytes = int(os.popen('du -sb "%s"' % file_name).read().split('\t')[0])
self.logger.debug(' Size in bytes : %i',size_bytes) 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,'',\ 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) backup_start=start,backup_end=fileisodate(file_name),status='OK',total_bytes=size_bytes,backup_location=file_name)
else: else:

View File

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

View File

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

View File

@ -12,21 +12,18 @@ The underlying INIConfig object can be accessed as cfg.data
""" """
import re 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 import six
from . import ini 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): class RawConfigParser(object):

View File

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

View File

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

View File

@ -1,5 +1,5 @@
from . import compat from . import compat
from .ini import LineContainer, EmptyLine from .ini import EmptyLine, LineContainer
def tidy(cfg): 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 six
requests requests
paramiko 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__ echo $VERSION > __VERSION__
rpmbuild -bb --buildroot $PWD/builddir -v --clean tis-tisbackup.spec rpmbuild -bb --buildroot $PWD/builddir -v --clean tis-tisbackup.spec
cp RPMS/*/*.rpm . cp RPMS/*/*.rpm .

View File

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

View File

@ -14,5 +14,3 @@ else
sleep 3 sleep 3
fi fi
echo $(date +%Y-%m-%d\ %H:%M:%S) : Fin Export TISBackup sur Disque USB : $target >> /var/log/tisbackup.log 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 ;type=rsync+ssh
;server_name=srvzimbra ;server_name=srvzimbra
;remote_dir=/ ;remote_dir=/
;exclude_list="/proc/**","/sys/**","/dev/**" ;exclude_list="/proc/**","/sys/**","/dev/**"
;private_key=/root/.ssh/id_rsa ;private_key=/root/.ssh/id_rsa
;ssh_port = 22 ;ssh_port = 22
@ -95,4 +95,3 @@ maximum_backup_age=30
;type=xcp-dump-metadata ;type=xcp-dump-metadata
;server_name=srvxen1 ;server_name=srvxen1
;private_key=/root/.ssh/id_rsa ;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 network_name=net-test
#start_vm=no #start_vm=no
#max_copies=3 #max_copies=3

View File

@ -4,4 +4,3 @@
# m h dom mon dow user command # 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 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 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] [general]
config_tisbackup= /etc/tis/tisbackup-config.ini config_tisbackup= /etc/tis/tisbackup-config.ini
sections= sections=
ADMIN_EMAIL=technique@tranquil-it-systems.fr ADMIN_EMAIL=technique@tranquil-it-systems.fr
base_config_dir= /etc/tis/ base_config_dir= /etc/tis/

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
[Unit] [Unit]
Description=tisbackup Description=tisbackup
[Service] [Service]
Type=simple Type=simple
ExecStart=huey_consumer.py -n tisbackup_gui.huey ExecStart=huey_consumer.py -n tisbackup_gui.huey
WorkingDirectory=/opt/tisbackup WorkingDirectory=/opt/tisbackup
[Install] [Install]
WantedBy=multi-user.target 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 () { $('[data-toggle="offcanvas"]').click(function () {
$('.row-offcanvas').toggleClass('active') $('.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 logging
import os
from huey import RedisHuey
from tisbackup import tis_backup from tisbackup import tis_backup
huey = RedisHuey('tisbackup', host='localhost') huey = RedisHuey('tisbackup', host='localhost')

View File

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

View File

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

View File

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

View File

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

View File

@ -18,41 +18,43 @@
# #
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
import datetime import datetime
import subprocess import os
import os,sys import sys
from os.path import isfile, join from os.path import isfile, join
tisbackup_root_dir = os.path.dirname(os.path.realpath(__file__)) 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, "lib"))
sys.path.insert(0,os.path.join(tisbackup_root_dir,'libtisbackup')) 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 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_mysql import backup_mysql
from libtisbackup.backup_rsync import backup_rsync
from libtisbackup.backup_rsync import backup_rsync_ssh # from libtisbackup.backup_vmdk import backup_vmdk
#from libtisbackup.backup_oracle import backup_oracle # from libtisbackup.backup_switch import backup_switch
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_null import backup_null from libtisbackup.backup_null import backup_null
from libtisbackup.backup_xcp_metadata import backup_xcp_metadata from libtisbackup.backup_pgsql import backup_pgsql
from libtisbackup.copy_vm_xcp import copy_vm_xcp from libtisbackup.backup_rsync import backup_rsync, backup_rsync_ssh
#from libtisbackup.backup_sqlserver import backup_sqlserver
# 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_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 %prog -c configfile action
TIS Files Backup system. TIS Files Backup system.
@ -67,52 +69,75 @@ action is either :
exportbackup : copy lastest OK backups from local to location defned by --exportdir parameter exportbackup : copy lastest OK backups from local to location defned by --exportdir parameter
register_existing : scan backup directories and add missing backups to database""" 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: 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.dry_run = dry_run
self.verbose = verbose self.verbose = verbose
self.backup_base_dir = backup_base_dir self.backup_base_dir = backup_base_dir
self.backup_base_dir = '' self.backup_base_dir = ""
self.backup_list = [] self.backup_list = []
self.dry_run = dry_run 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() ini.change_comment_syntax()
cp = ConfigParser() cp = ConfigParser()
cp.read(filename) cp.read(filename)
if not self.backup_base_dir: 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): 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) os.makedirs(self.backup_base_dir)
self.logger.debug("backup directory : " + 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(): for section in cp.sections():
if (section != 'global'): if section != "global":
self.logger.debug("reading backup config " + section) self.logger.debug("reading backup config " + section)
backup_item = None backup_item = None
type = cp.get(section,'type') type = cp.get(section, "type")
backup_item = backup_drivers[type](backup_name=section, backup_item = backup_drivers[type](
backup_dir=os.path.join(self.backup_base_dir,section),dbstat=self.dbstat,dry_run=self.dry_run) 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.read_config(cp)
backup_item.verbose = self.verbose backup_item.verbose = self.verbose
@ -122,35 +147,34 @@ class tis_backup:
# TODO socket.gethostbyaddr('64.236.16.20') # TODO socket.gethostbyaddr('64.236.16.20')
# TODO limit backup to one backup on the command line # TODO limit backup to one backup on the command line
def checknagios(self, sections=[]):
def checknagios(self,sections=[]):
try: try:
if not sections: if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list] 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: try:
worst_nagiosstatus = None worst_nagiosstatus = None
ok = [] ok = []
warning = [] warning = []
critical = [] critical = []
unknown = [] unknown = []
nagiosoutput = '' nagiosoutput = ""
for backup_item in self.backup_list: for backup_item in self.backup_list:
if not sections or backup_item.backup_name in sections: if not sections or backup_item.backup_name in sections:
(nagiosstatus,log) = backup_item.checknagios() (nagiosstatus, log) = backup_item.checknagios()
if nagiosstatus == nagiosStateCritical: if nagiosstatus == nagiosStateCritical:
critical.append((backup_item.backup_name,log)) critical.append((backup_item.backup_name, log))
elif nagiosstatus == nagiosStateWarning : elif nagiosstatus == nagiosStateWarning:
warning.append((backup_item.backup_name,log)) warning.append((backup_item.backup_name, log))
elif nagiosstatus == nagiosStateOk: elif nagiosstatus == nagiosStateOk:
ok.append((backup_item.backup_name,log)) ok.append((backup_item.backup_name, log))
else: else:
unknown.append((backup_item.backup_name,log)) unknown.append((backup_item.backup_name, log))
self.logger.debug('[%s] nagios:"%i" log: %s',backup_item.backup_name,nagiosstatus,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: 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 worst_nagiosstatus = nagiosStateUnknown
nagiosoutput = 'UNKNOWN : Unknown backup sections "%s"' % sections nagiosoutput = 'UNKNOWN : Unknown backup sections "%s"' % sections
@ -159,156 +183,154 @@ class tis_backup:
if unknown: if unknown:
if not worst_nagiosstatus: if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateUnknown 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) globallog.extend(unknown)
if critical: if critical:
if not worst_nagiosstatus: if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateCritical 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) globallog.extend(critical)
if warning: if warning:
if not worst_nagiosstatus: if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateWarning 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) globallog.extend(warning)
if ok: if ok:
if not worst_nagiosstatus: if not worst_nagiosstatus:
worst_nagiosstatus = nagiosStateOk 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) globallog.extend(ok)
if worst_nagiosstatus == nagiosStateOk: if worst_nagiosstatus == nagiosStateOk:
nagiosoutput = 'ALL backups OK %s' % (','.join(sections)) nagiosoutput = "ALL backups OK %s" % (",".join(sections))
except BaseException as e: except BaseException as e:
worst_nagiosstatus = nagiosStateCritical worst_nagiosstatus = nagiosStateCritical
nagiosoutput = 'EXCEPTION',"Critical : %s" % str(e) nagiosoutput = "EXCEPTION", "Critical : %s" % str(e)
raise raise
finally: finally:
self.logger.debug('worst nagios status :"%i"',worst_nagiosstatus) self.logger.debug('worst nagios status :"%i"', worst_nagiosstatus)
print('%s (tisbackup V%s)' %(nagiosoutput,version)) print("%s (tisbackup V%s)" % (nagiosoutput, version))
print('\n'.join(["[%s]:%s" % (l[0],l[1]) for l in globallog])) print("\n".join(["[%s]:%s" % (log_elem[0], log_elem[1]) for log_elem in globallog]))
sys.exit(worst_nagiosstatus) sys.exit(worst_nagiosstatus)
def process_backup(self,sections=[]): def process_backup(self, sections=[]):
processed = [] processed = []
errors = [] errors = []
if not sections: if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list] 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: for backup_item in self.backup_list:
if not sections or backup_item.backup_name in sections: if not sections or backup_item.backup_name in sections:
try: try:
assert(isinstance(backup_item,backup_generic)) assert isinstance(backup_item, backup_generic)
self.logger.info('Processing [%s]',(backup_item.backup_name)) self.logger.info("Processing [%s]", (backup_item.backup_name))
stats = backup_item.process_backup() stats = backup_item.process_backup()
processed.append((backup_item.backup_name,stats)) processed.append((backup_item.backup_name, stats))
except BaseException as e: except BaseException as e:
self.logger.critical('Backup [%s] processed with error : %s',backup_item.backup_name,e) self.logger.critical("Backup [%s] processed with error : %s", backup_item.backup_name, e)
errors.append((backup_item.backup_name,str(e))) errors.append((backup_item.backup_name, str(e)))
if not processed and not errors: 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: else:
if processed: 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: 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 = [] processed = []
errors = [] errors = []
if not sections: if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list] 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: for backup_item in self.backup_list:
if backup_item.backup_name in sections: if backup_item.backup_name in sections:
try: try:
assert(isinstance(backup_item,backup_generic)) assert isinstance(backup_item, backup_generic)
self.logger.info('Processing [%s]',(backup_item.backup_name)) self.logger.info("Processing [%s]", (backup_item.backup_name))
stats = backup_item.export_latestbackup(destdir=exportdir) 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: except BaseException as e:
self.logger.critical('Export Backup [%s] processed with error : %s',backup_item.backup_name,e) self.logger.critical("Export Backup [%s] processed with error : %s", backup_item.backup_name, e)
errors.append((backup_item.backup_name,str(e))) errors.append((backup_item.backup_name, str(e)))
if not processed and not errors: 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: else:
if processed: 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: 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 = [] processed = []
errors = [] errors = []
# before mindate, backup is too old # before mindate, backup is too old
mindate = datetime2isodate((datetime.datetime.now() - datetime.timedelta(hours=maxage_hours))) 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 select distinct backup_name as bname
from stats from stats
where status="OK" and backup_start>=?""",(mindate,)) 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])
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: 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: for backup_item in self.backup_list:
if backup_item.backup_name in failed_backups_names: if backup_item.backup_name in failed_backups_names:
try: try:
assert(isinstance(backup_item,backup_generic)) assert isinstance(backup_item, backup_generic)
self.logger.info('Processing [%s]',(backup_item.backup_name)) self.logger.info("Processing [%s]", (backup_item.backup_name))
stats = backup_item.process_backup() stats = backup_item.process_backup()
processed.append((backup_item.backup_name,stats)) processed.append((backup_item.backup_name, stats))
except BaseException as e: except BaseException as e:
self.logger.critical('Backup [%s] not processed, error : %s',backup_item.backup_name,e) self.logger.critical("Backup [%s] not processed, error : %s", backup_item.backup_name, e)
errors.append((backup_item.backup_name,str(e))) errors.append((backup_item.backup_name, str(e)))
if not processed and not errors: 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: else:
if processed: 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: 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: 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=[]):
def cleanup_backup_section(self,sections = []):
log = ''
processed = False processed = False
if not sections: if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list] 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: for backup_item in self.backup_list:
if backup_item.backup_name in sections: if backup_item.backup_name in sections:
try: try:
assert(isinstance(backup_item,backup_generic)) assert isinstance(backup_item, backup_generic)
self.logger.info('Processing cleanup of [%s]',(backup_item.backup_name)) self.logger.info("Processing cleanup of [%s]", (backup_item.backup_name))
backup_item.cleanup_backup() backup_item.cleanup_backup()
processed = True processed = True
except BaseException as e: 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: 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: if not sections:
sections = [backup_item.backup_name for backup_item in self.backup_list] 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: for backup_item in self.backup_list:
if backup_item.backup_name in sections: if backup_item.backup_name in sections:
backup_item.register_existingbackups() backup_item.register_existingbackups()
@ -316,26 +338,26 @@ class tis_backup:
def html_report(self): def html_report(self):
for backup_item in self.backup_list: for backup_item in self.backup_list:
if not section or section == backup_item.backup_name: 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: if not maxage_hours:
maxage_hours = backup_item.maximum_backup_age maxage_hours = backup_item.maximum_backup_age
(nagiosstatus,log) = backup_item.checknagios(maxage_hours=maxage_hours) (nagiosstatus, log) = backup_item.checknagios(maxage_hours=maxage_hours)
globallog.append('[%s] %s' % (backup_item.backup_name,log)) globallog.append("[%s] %s" % (backup_item.backup_name, log))
self.logger.debug('[%s] nagios:"%i" log: %s',backup_item.backup_name,nagiosstatus,log) self.logger.debug('[%s] nagios:"%i" log: %s', backup_item.backup_name, nagiosstatus, log)
processed = True # processed = True
if nagiosstatus >= worst_nagiosstatus: # if nagiosstatus >= worst_nagiosstatus:
worst_nagiosstatus = nagiosstatus # worst_nagiosstatus = nagiosstatus
def main(): def main():
(options,args)=parser.parse_args() (options, args) = parser.parse_args()
if len(args) != 1: if len(args) != 1:
print("ERROR : You must provide one action to perform") print("ERROR : You must provide one action to perform")
parser.print_usage() parser.print_usage()
sys.exit(2) 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 # options
action = args[0] action = args[0]
@ -344,23 +366,23 @@ def main():
print(backup_drivers[t].get_help()) print(backup_drivers[t].get_help())
sys.exit(0) sys.exit(0)
config_file =options.config config_file = options.config
dry_run = options.dry_run dry_run = options.dry_run
verbose = options.verbose verbose = options.verbose
loglevel = options.loglevel loglevel = options.loglevel
# setup Logger # setup Logger
logger = logging.getLogger('tisbackup') logger = logging.getLogger("tisbackup")
hdlr = logging.StreamHandler() 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) logger.addHandler(hdlr)
# set loglevel # 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) numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int): if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % loglevel) raise ValueError("Invalid log level: %s" % loglevel)
logger.setLevel(numeric_level) logger.setLevel(numeric_level)
# Config file # Config file
@ -371,36 +393,36 @@ def main():
cp = ConfigParser() cp = ConfigParser()
cp.read(config_file) cp.read(config_file)
backup_base_dir = options.backup_base_dir or cp.get('global','backup_base_dir') backup_base_dir = options.backup_base_dir or cp.get("global", "backup_base_dir")
log_dir = os.path.join(backup_base_dir,'log') log_dir = os.path.join(backup_base_dir, "log")
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir)
# if we run the nagios check, we don't create log file, everything is piped to stdout # if we run the nagios check, we don't create log file, everything is piped to stdout
if action!='checknagios': if action != "checknagios":
try: try:
hdlr = logging.FileHandler(os.path.join(log_dir,'tisbackup_%s.log' % (backup_start_date))) 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.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
logger.addHandler(hdlr) logger.addHandler(hdlr)
except IOError as e: 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.") logger.warning("No space left on device, disabling file logging.")
else: else:
raise e raise e
# Main # 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.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] all_sections = [backup_item.backup_name for backup_item in backup.backup_list]
if not backup_sections: if not backup_sections:
backup_sections = all_sections backup_sections = all_sections
else: else:
for b in backup_sections: for b in backup_sections:
if not b in all_sections: if b not in all_sections:
raise Exception('Section %s is not defined in config file' % b) raise Exception("Section %s is not defined in config file" % b)
if dry_run: if dry_run:
logger.warning("WARNING : DRY RUN, nothing will be done, just printing on screen...") logger.warning("WARNING : DRY RUN, nothing will be done, just printing on screen...")
@ -409,23 +431,22 @@ def main():
backup.process_backup(backup_sections) backup.process_backup(backup_sections)
elif action == "exportbackup": elif action == "exportbackup":
if not options.exportdir: if not options.exportdir:
raise Exception('No export directory supplied dor exportbackup action') raise Exception("No export directory supplied dor exportbackup action")
backup.export_backups(backup_sections,options.exportdir) backup.export_backups(backup_sections, options.exportdir)
elif action == "cleanup": elif action == "cleanup":
backup.cleanup_backup_section(backup_sections) backup.cleanup_backup_section(backup_sections)
elif action == "checknagios": elif action == "checknagios":
backup.checknagios(backup_sections) backup.checknagios(backup_sections)
elif action == "dumpstat": elif action == "dumpstat":
for s in backup_sections: for s in backup_sections:
backup.dbstat.last_backups(s,count=options.statscount) backup.dbstat.last_backups(s, count=options.statscount)
elif action == "retryfailed": elif action == "retryfailed":
backup.retry_failed_backups() backup.retry_failed_backups()
elif action == "register_existing": elif action == "register_existing":
backup.register_existingbackups(backup_sections) backup.register_existingbackups(backup_sections)
else: else:
logger.error('Unhandled action "%s", quitting...',action) logger.error('Unhandled action "%s", quitting...', action)
sys.exit(1) sys.exit(1)

View File

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