mirror of
https://github.com/uroni/urbackup-server-python-web-api-wrapper.git
synced 2025-10-26 11:38:16 +00:00
471 lines
12 KiB
Python
471 lines
12 KiB
Python
import json
|
|
import requests
|
|
from base64 import b64encode
|
|
import hashlib
|
|
import socket
|
|
import shutil
|
|
import os
|
|
import binascii
|
|
import logging
|
|
|
|
logger = logging.getLogger('urbackup-server-python-api-wrapper')
|
|
|
|
|
|
"""Backwards compatible imports"""
|
|
try:
|
|
import urllib
|
|
except ImportError:
|
|
import urllib.parse
|
|
|
|
|
|
class urbackup_server:
|
|
|
|
#If you have basic authentication via .htpasswd
|
|
server_basic_username = ''
|
|
server_basic_password = ''
|
|
|
|
action_incr_file = 1
|
|
action_full_file = 2
|
|
action_incr_image = 3
|
|
action_full_image = 4
|
|
action_resumed_incr_file = 5
|
|
action_resumed_full_file = 6
|
|
action_file_restore = 8
|
|
action_image_restore = 9
|
|
action_client_update = 10
|
|
action_check_db_integrity = 11
|
|
action_backup_db = 12
|
|
action_recalc_stats = 13
|
|
|
|
_session = ''
|
|
_logged_in = False
|
|
_lastlogid = 0
|
|
|
|
def __init__(self, url, username, password):
|
|
self._server_url = url
|
|
self._server_username = username
|
|
self._server_password = password
|
|
|
|
def _get_response(self, action, params, method='POST'):
|
|
|
|
headers = {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json; charset=UTF-8'
|
|
}
|
|
|
|
if('server_basic_username' in globals() and len(self.server_basic_username)>0):
|
|
userAndPass = b64encode(
|
|
str.encode('%s:%s' % (self.server_basic_username, self.server_basic_password))
|
|
).decode('ascii')
|
|
headers['Authorization'] = 'Basic %s' % userAndPass
|
|
|
|
try:
|
|
action_url = '%s?%s' % (self._server_url, urllib.urlencode({'a': action}))
|
|
except AttributeError:
|
|
action_url = '%s?%s' % (self._server_url, urllib.parse.urlencode({'a': action}))
|
|
|
|
if(len(self._session)>0):
|
|
params['ses'] = self._session
|
|
|
|
if method == 'POST':
|
|
r = requests.post(
|
|
action_url,
|
|
data=params
|
|
)
|
|
elif method == 'GET':
|
|
r = requests.get(
|
|
action_url,
|
|
params=params
|
|
)
|
|
else:
|
|
raise Exception('Request with method \'%s\' has not been implemented yet.' % method)
|
|
|
|
return r
|
|
|
|
def _get_json(self, action, params = {}):
|
|
tries = 50
|
|
|
|
while tries>0:
|
|
response = self._get_response(action, params)
|
|
|
|
if(response.status_code == 200):
|
|
break
|
|
|
|
tries -= 1
|
|
if tries <= 0:
|
|
return None
|
|
else:
|
|
logger.error('API call failed. Retrying...')
|
|
|
|
return response.json()
|
|
|
|
def _download_file(self, action, outputfn, params):
|
|
|
|
req = self._get_response(action, params, 'GET');
|
|
|
|
if req.status_code != 200:
|
|
return False
|
|
|
|
with open(outputfn, 'wb') as stream:
|
|
for chunk in req.iter_content(chunk_size=128):
|
|
stream.write(chunk)
|
|
|
|
return True
|
|
|
|
def _md5(self, s):
|
|
return hashlib.md5(s.encode()).hexdigest()
|
|
|
|
def login(self):
|
|
|
|
if not self._logged_in:
|
|
|
|
logger.debug('Trying anonymous login...')
|
|
|
|
login = self._get_json('login', {});
|
|
|
|
if not login or 'success' not in login or not login['success'] :
|
|
logger.debug('Logging in...')
|
|
|
|
salt = self._get_json('salt', {'username': self._server_username})
|
|
if not salt or not ('ses' in salt):
|
|
logger.warning('Username does not exist')
|
|
return False
|
|
|
|
self._session = salt['ses'];
|
|
|
|
if 'salt' in salt:
|
|
password_md5_bin = hashlib.md5((salt['salt']+self._server_password).encode()).digest()
|
|
password_md5 = binascii.hexlify(password_md5_bin).decode()
|
|
|
|
if 'pbkdf2_rounds' in salt:
|
|
pbkdf2_rounds = int(salt['pbkdf2_rounds'])
|
|
if pbkdf2_rounds>0:
|
|
password_md5 = binascii.hexlify(hashlib.pbkdf2_hmac('sha256', password_md5_bin,
|
|
salt['salt'].encode(), pbkdf2_rounds)).decode()
|
|
|
|
password_md5 = self._md5(salt['rnd']+password_md5)
|
|
|
|
login = self._get_json('login', {
|
|
'username': self._server_username,
|
|
'password': password_md5 })
|
|
|
|
if not login or 'success' not in login or not login['success']:
|
|
logger.warning('Error during login. Password wrong?')
|
|
return False
|
|
|
|
else:
|
|
self._logged_in = True
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
self._logged_in = True
|
|
self._session = login['session'];
|
|
return True
|
|
else:
|
|
return True
|
|
|
|
def get_client_status(self, clientname):
|
|
|
|
if not self.login():
|
|
return None
|
|
|
|
status = self._get_json('status')
|
|
|
|
if not status:
|
|
return None
|
|
|
|
if not 'status' in status:
|
|
return None
|
|
|
|
for client in status['status']:
|
|
|
|
if client['name'] == clientname:
|
|
|
|
return client;
|
|
|
|
logger.warning('Could not find client status. No permission?')
|
|
return None
|
|
|
|
def download_installer(self, installer_fn, new_clientname, os='linux'):
|
|
"""Download installer for os, defaults to linux"""
|
|
|
|
if not os.lower() in ['linux', 'osx', 'mac', 'windows']:
|
|
raise Exception('OS not supported')
|
|
|
|
if not self.login():
|
|
return False
|
|
|
|
new_client = self._get_json('add_client', {'clientname': new_clientname})
|
|
|
|
if 'already_exists' in new_client:
|
|
|
|
status = self.get_client_status(new_clientname)
|
|
|
|
if status == None:
|
|
return False
|
|
|
|
return self._download_file('download_client', installer_fn, {'clientid': status['id'] })
|
|
|
|
|
|
if not 'new_authkey' in new_client:
|
|
return False
|
|
|
|
return self._download_file('download_client', installer_fn,
|
|
{
|
|
'clientid': new_client['new_clientid'],
|
|
'authkey': new_client['new_authkey'],
|
|
'os': os
|
|
})
|
|
|
|
def add_client(self, clientname):
|
|
|
|
if not self.login():
|
|
return None
|
|
|
|
ret = self._get_json('add_client', { 'clientname': clientname})
|
|
if ret==None or 'already_exists' in ret:
|
|
return None
|
|
|
|
return ret
|
|
|
|
def get_global_settings(self):
|
|
if not self.login():
|
|
return None
|
|
|
|
settings = self._get_json('settings', {'sa': 'general'} )
|
|
|
|
if not settings or not 'settings' in settings:
|
|
return None
|
|
|
|
return settings['settings']
|
|
|
|
def set_global_setting(self, key, new_value):
|
|
if not self.login():
|
|
return False
|
|
|
|
settings = self._get_json('settings', {'sa': 'general'} )
|
|
|
|
if not settings or not 'settings' in settings:
|
|
return False
|
|
|
|
settings['settings'][key] = new_value
|
|
settings['settings']['sa'] = 'general_save'
|
|
|
|
ret = self._get_json('settings', settings['settings'])
|
|
|
|
return ret != None and 'saved_ok' in ret
|
|
|
|
def get_client_settings(self, clientname):
|
|
|
|
if not self.login():
|
|
return None
|
|
|
|
client = self.get_client_status(clientname)
|
|
|
|
if client == None:
|
|
return None
|
|
|
|
clientid = client['id'];
|
|
|
|
settings = self._get_json('settings', {'sa': 'clientsettings',
|
|
't_clientid': clientid})
|
|
|
|
if not settings or not 'settings' in settings:
|
|
return None
|
|
|
|
return settings['settings']
|
|
|
|
def change_client_setting(self, clientname, key, new_value):
|
|
if not self.login():
|
|
return False
|
|
|
|
client = self.get_client_status(clientname)
|
|
|
|
if client == None:
|
|
return False
|
|
|
|
clientid = client['id'];
|
|
|
|
settings = self._get_json('settings', {'sa': 'clientsettings',
|
|
't_clientid': clientid})
|
|
|
|
if not settings or not 'settings' in settings:
|
|
return False
|
|
|
|
settings['settings'][key] = new_value
|
|
settings['settings']['overwrite'] = 'true'
|
|
settings['settings']['sa'] = 'clientsettings_save'
|
|
settings['settings']['t_clientid'] = clientid
|
|
|
|
ret = self._get_json('settings', settings['settings'])
|
|
|
|
return ret != None and 'saved_ok' in ret
|
|
|
|
def get_client_authkey(self, clientname):
|
|
|
|
if not self.login():
|
|
return None
|
|
|
|
settings = self.get_client_settings(clientname)
|
|
|
|
if settings:
|
|
return settings['internet_authkey']
|
|
|
|
return None
|
|
|
|
def get_server_identity(self):
|
|
|
|
if not self.login():
|
|
return None
|
|
|
|
status = self._get_json('status')
|
|
|
|
if not status:
|
|
return None
|
|
|
|
if not 'server_identity' in status:
|
|
return None
|
|
|
|
return status['server_identity']
|
|
|
|
def get_status(self):
|
|
if not self.login():
|
|
return None
|
|
|
|
status = self._get_json('status')
|
|
|
|
if not status:
|
|
return None
|
|
|
|
if not 'status' in status:
|
|
return None
|
|
|
|
return status['status']
|
|
|
|
def get_livelog(self, clientid = 0):
|
|
if not self.login():
|
|
return None
|
|
|
|
log = self._get_json('livelog', {'clientid': clientid, 'lastid': self._lastlogid})
|
|
|
|
if not log:
|
|
return None
|
|
|
|
if not 'logdata' in log:
|
|
return None
|
|
|
|
self._lastlogid = log['logdata'][-1]['id']
|
|
|
|
return log['logdata']
|
|
|
|
def get_usage(self):
|
|
if not self.login():
|
|
return None
|
|
|
|
usage = self._get_json('usage')
|
|
|
|
if not usage:
|
|
return None
|
|
|
|
if not 'usage' in usage:
|
|
return None
|
|
|
|
return usage['usage']
|
|
|
|
def get_extra_clients(self):
|
|
if not self.login():
|
|
return None
|
|
|
|
status = self._get_json('status')
|
|
|
|
if not status:
|
|
return None
|
|
|
|
if not 'extra_clients' in status:
|
|
return None
|
|
|
|
return status['extra_clients']
|
|
|
|
def _start_backup(self, clientname, backup_type):
|
|
|
|
client_info = self.get_client_status(clientname)
|
|
|
|
if not client_info:
|
|
return False
|
|
|
|
ret = self._get_json('start_backup', {'start_client': client_info['id'],
|
|
'start_type': backup_type } );
|
|
|
|
if ( ret == None
|
|
or 'result' not in ret
|
|
or len(ret['result'])!=1
|
|
or 'start_ok' not in ret['result'][0]
|
|
or not ret['result'][0]['start_ok']):
|
|
return False
|
|
|
|
return True
|
|
|
|
def start_incr_file_backup(self, clientname):
|
|
return self._start_backup(clientname, 'incr_file');
|
|
|
|
def start_full_file_backup(self, clientname):
|
|
return self._start_backup(clientname, 'full_file');
|
|
|
|
def start_incr_image_backup(self, clientname):
|
|
return self._start_backup(clientname, 'incr_image');
|
|
|
|
def start_full_image_backup(self, clientname):
|
|
return self._start_backup(clientname, 'full_image');
|
|
|
|
def add_extra_client(self, addr):
|
|
if not self.login():
|
|
return None
|
|
|
|
ret = self._get_json('status', {'hostname': addr } )
|
|
|
|
if not ret:
|
|
return False
|
|
|
|
return True
|
|
|
|
def remove_extra_client(self, ecid):
|
|
if not self.login():
|
|
return None
|
|
|
|
ret = self._get_json('status', {'hostname': ecid,
|
|
'remove': 'true' })
|
|
|
|
if not ret:
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_actions(self):
|
|
if not self.login():
|
|
return None
|
|
|
|
ret = self._get_json('progress')
|
|
|
|
if not ret or not 'progress' in ret:
|
|
return None
|
|
|
|
return ret['progress']
|
|
|
|
def stop_action(self, action):
|
|
if (not 'clientid' in action
|
|
or not 'id' in action):
|
|
return False
|
|
|
|
if not self.login():
|
|
return None
|
|
|
|
ret = self._get_json('progress',
|
|
{'stop_clientid': action['clientid'],
|
|
'stop_id': action['id']})
|
|
|
|
if not ret or not 'progress' in ret:
|
|
return False
|
|
|
|
return True
|