I don't know if this exists elsewhere but trying to search for "backup" and only found loads of questions but no solutions. Krydos referenced a dead wiki in this post in 2019 https://helionet.org/index/topic/35348-backup-heliohost-account-to-a-cloud-storage/#findComment-157124 . So without a solution here is what I did:
1) Created a folder under my domain for backups.
2) Under plex backup manager, Remote Storage Settings; I set this up to save backups to this folder.
3) Again using plex, for a full backup, click on Account, backup my account and websites, and then Schedule Backup. I setup a daily full backup to save into the FTP(S).
4) In the backups folder I created under my domain, I put this python file:
#!/usr/bin/python3.12
print('Content-type: text/html\r\n\r')
import json
import os
import requests
import sys
session: requests.Session = requests.Session()
#--------------------------------------------------------------------------
#CONSTANTS
local_backup_files_folder = "."
dropbox_app_key = ""
dropbox_app_secret = ""
dropbox_basic_token = ""
dropbox_access_token = ""
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def getLocalBackupFiles(local_backup_files_folder) -> dict[str, int]:
local_files = {}
all_local_files = os.listdir(local_backup_files_folder)
for f in all_local_files:
if os.path.isfile(os.path.join(local_backup_files_folder, f)) and f.endswith('.tar'):
local_files[f] = os.path.getsize(f)
return local_files
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def generateHeaders(auth_method) -> dict[str, str]:
if auth_method == 'basic':
headers: dict[str, str] = {
'Authorization': 'Basic ' + dropbox_basic_token,
'Content-Type': 'application/json'
}
if auth_method == 'bearer':
headers: dict[str, str] = {
'Authorization': 'Bearer ' + dropbox_access_token,
'Content-Type': 'application/json'
}
if auth_method == 'upload':
headers: dict[str, str] = {
'Authorization': 'Bearer ' + dropbox_access_token,
'Content-Type': 'application/octet-stream'
}
return headers
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def checkDropboxConnection() -> bool:
headers: dict[str, str] = generateHeaders("basic")
url: str='https://api.dropboxapi.com/2/check/app'
data = json.dumps({"query":"heliohost"})
res: requests.Response = session.post(url, data=data, headers=headers, timeout=10, allow_redirects=False)
if res.status_code == 200:
if res.json()['result'] == "heliohost":
return True
else:
return False
else:
return False
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def listDropboxFolder() -> dict[str, int]:
headers: dict[str, str] = generateHeaders("bearer")
url: str='https://api.dropboxapi.com/2/files/list_folder'
data = json.dumps({
"path": ""
})
res: requests.Response = session.post(url, data=data, headers=headers, timeout=10, allow_redirects=False)
if res.status_code == 200:
if "entries" in res.text:
number_of_dropbox_files = len(res.json()['entries'])
if number_of_dropbox_files > 0:
#print(str(number_of_dropbox_files) + ' files in Dropbox Folder')
print("<br>")
dropbox_tar_files = {}
for f in res.json()['entries']:
#print(f)
print("<br>")
if f['name'].endswith('.tar'):
dropbox_tar_files[f['name']] = f['size']
else:
print('No files in Dropbox Folder')
print("<br>")
dropbox_tar_files = []
return dropbox_tar_files
else:
sys.exit('File list has wrong data')
else:
sys.exit('Cannot get file list')
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def checkMissingFiles(local_files, dropbox_tar_files):
for f in local_files:
#print(f)
print("<br>")
if f in dropbox_tar_files:
print(f + " is in dropbox")
print("<br>")
if local_files[f] == dropbox_tar_files[f]:
print(f + " size matches")
print("<br>")
continue
else:
print(f + " size mitchmatch in dropbox")
print("<br>")
result = uploadMissingFiles(f)
if result == True:
print(f + " uploaded successfully")
print("<br>")
else:
print(f + " is not in dropbox")
print("<br>")
result = uploadMissingFiles(f)
if result == True:
print(f + " uploaded successfully")
print("<br>")
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def uploadMissingFiles(f) -> bool:
headers: dict[str, str] = generateHeaders("upload")
headers['Dropbox-API-Arg'] = '{"autorename":false,"mode":"add","mute":false,"path":"/' + f + '","strict_conflict":false}'
url: str='https://content.dropboxapi.com/2/files/upload'
data = open(f, "rb").read()
res: requests.Response = session.post(url, data=data, headers=headers, timeout=10, allow_redirects=False)
if res.status_code == 200:
if res.json()['name'] == f:
return True
else:
sys.exit('Error uploading')
else:
sys.exit('Error uploading')
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
def deleteUploadedFiles(local_files, dropbox_tar_files):
for f in dropbox_tar_files:
if f in local_files:
print(f + " exists in dropbox")
print("<br>")
if local_files[f] == dropbox_tar_files[f]:
print(f + " size matches")
print("<br>")
os.remove(f)
else:
print(f + " size doesn't match")
print("<br>")
sys.exit('Size mismatch after upload')
else:
print(f + " is not in dropbox")
print("<br>")
sys.exit('File not in dropbox after upload')
#--------------------------------------------------------------------------
print("Starting")
print("<br>")
local_files= getLocalBackupFiles(local_backup_files_folder)
if len(local_files) == 0: exit(0)
print(local_files)
print("<br>")
check = checkDropboxConnection()
if check:
dropbox_tar_files = listDropboxFolder()
else:
sys.exit('Error connecting to DropBox')
print(dropbox_tar_files)
print("<br>")
print("------------------------------------------")
print("<br>")
checkMissingFiles(local_files, dropbox_tar_files)
dropbox_tar_files = listDropboxFolder()
print("------------------------------------------")
print("<br>")
deleteUploadedFiles(local_files, dropbox_tar_files)
print("------------------------------------------")
print("<br>")
local_files= getLocalBackupFiles(local_backup_files_folder)
print(local_files)
print("<br>")
Replace the:
dropbox_app_key = ""
dropbox_app_secret = ""
dropbox_basic_token = ""
dropbox_access_token = ""
with you own values.
5) Setup a cron job to fetch the python url and execute the script.
If you're having issues running python in this folder I used the .htaccess file in the folder:
Options +ExecCGI
AddHandler cgi-script .py
DirectoryIndex backup_export.py
where backup_export.py is the name of the script above.
Hope this is of use. If anyone improves on this and wants to share with the rest of us please go ahead.