Last active
November 2, 2017 07:35
-
-
Save moteus/6574437c2374a4abde7b to your computer and use it in GitHub Desktop.
Backup FusionPBX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local FS_DIR = 'c:/FreeSWITCH' | |
local FUSION_DIR = 'c:/wamp/www/fusionpbx' | |
local NGINX_DIR = 'c:/nginxwin' | |
local PHP_DIR = NGINX_DIR .. '/php_5.4' | |
-- local PASSWORD = '' -- password to pgsql (optional) | |
-- local BACKUP_DIR = '' -- target directory (default cwd) | |
----------------------------------------------------------- | |
local path = require "path" | |
local date = require "date" | |
local luasql = require "odbc.luasql" | |
local walk_old_files = require "walk_old_files" | |
local walk_empty_dirs = require "walk_empty_dirs" | |
local iconv = require "iconv" | |
local log = require "log".new( | |
require "log.writer.list".new( | |
require "log.writer.file.by_day".new("./logs", "backup.log", math.huge) | |
-- ,require "log.writer.stdout".new() | |
), | |
require "log.formatter.mix".new() | |
) | |
local LOG_CP = "cp1251" | |
local J = path.join | |
----------------------------------------------------------- | |
local exec do | |
local path = require "path" | |
local lua_version_t | |
local function lua_version() | |
if not lua_version_t then | |
local version = rawget(_G,"_VERSION") | |
local maj,min = version:match("^Lua (%d+)%.(%d+)$") | |
if maj then lua_version_t = {tonumber(maj),tonumber(min)} | |
elseif not math.mod then lua_version_t = {5,2} | |
elseif table.pack and not pack then lua_version_t = {5,2} | |
else lua_version_t = {5,2} end | |
end | |
return lua_version_t[1], lua_version_t[2] | |
end | |
local LUA_MAJOR, LUA_MINOR = lua_version() | |
local LUA_VERSION = LUA_MAJOR * 100 + LUA_MINOR | |
local LUA_52 = 502 | |
local IS_WINDOWS = package.config:sub(1,1) == '\\' | |
local function read_file(n) | |
local f, e = io.open(n, "r") | |
if not f then return nil, e end | |
local d, e = f:read("*all") | |
f:close() | |
return d, e | |
end | |
exec = function(cwd, cmd, ...) | |
assert(cmd, 'No command was provided') | |
local tmpfile = assert(path.tmpname()) | |
cmd = path.quote(cmd) | |
if ... then | |
cmd = cmd .. ' ' .. string.format(...) .. ' ' | |
if IS_WINDOWS then cmd = path.quote(cmd) end | |
end | |
cmd = cmd .. ' >' .. path.quote(tmpfile) .. ' 2>&1' | |
local p | |
if cwd and (cwd ~= "") and (cwd ~= ".") then | |
p = path.currentdir() | |
path.chdir(cwd) | |
end | |
local res1,res2,res2 = os.execute(cmd) | |
if p then path.chdir(p) end | |
local data = read_file(tmpfile) | |
path.remove(tmpfile) | |
if LUA_VERSION < LUA_52 then | |
return res1==0, res1, data | |
end | |
return res1, res2, data | |
end | |
end | |
----------------------------------------------------------- | |
----------------------------------------------------------- | |
local zip_dir do | |
local IS_WINDOWS = package.config:sub(1,1) == '\\' | |
local ZipWriter = require "ZipWriter" | |
local PATH = require "path" | |
local TEXT_EXT = {".lua", ".txt", ".c", ".cpp", ".h", ".hpp", ".pas", ".cxx", ".me"} | |
local function isin(s, t) | |
local s = s:lower() | |
for _, v in ipairs(t) do if s == v then return true end end | |
end | |
local function make_file_desc(path) | |
local fullpath = PATH.fullpath(path) | |
local desc = {} | |
desc.isfile = PATH.isfile(fullpath) | |
desc.isdir = PATH.isdir(fullpath) | |
if not (desc.isfile or desc.isdir) then error('file not found :' .. path .. ' (' .. fullpath .. ')') end | |
desc.mtime = PATH.mtime(fullpath) | |
desc.ctime = PATH.ctime(fullpath) | |
desc.atime = PATH.atime(fullpath) | |
local ext = desc.isfile and PATH.extension(fullpath) | |
desc.istext = ext and isin(ext, TEXT_EXT) | |
desc.exattrib = PATH.fileattrib and PATH.fileattrib(fullpath) | |
return desc | |
end | |
local function file_reader(path, chunk_size) | |
local desc = assert(make_file_desc(path)) | |
local f = desc.isfile and assert(io.open(path, 'rb')) | |
chunk_size = chunk_size or 4096 | |
return desc, desc.isfile and function() | |
local chunk = f:read(chunk_size) | |
if chunk then return chunk end | |
f:close() | |
end | |
end | |
local function file_writer(path) | |
local f = assert(io.open(path, 'wb+')) | |
return | |
function(chunk) | |
if not chunk then return f:close() end | |
f:write(chunk) | |
end | |
,function(...) return f:seek(...) end | |
end | |
function zip_dir(oFile, mask, opt) | |
local mask = mask or "./*.*" | |
local mask = PATH.fullpath(mask) | |
local base = PATH.dirname(mask) | |
if opt then | |
local t = {} | |
for k, v in pairs(opt) do t[k] = v end | |
opt = t | |
opt.skipdirs = true | |
else opt = {recurse=true; skipdirs=true} end | |
if PATH.extension(oFile):lower() ~= '.zip' then | |
oFile = oFile .. '.zip' | |
end | |
local files = {} | |
PATH.each(mask, function(fullpath) | |
local relpath = string.sub(fullpath, #base + 1) | |
table.insert(files,{fullpath, relpath}) | |
end, opt) | |
local writer = ZipWriter.new{ | |
level = ZipWriter.COMPRESSION_LEVEL.DEFAULT; | |
zip64 = false; | |
utf8 = false; | |
} | |
writer:open_writer(file_writer(oFile)) | |
local error_count, last_error = 0 | |
for _, t in ipairs(files) do | |
local fullpath, fileName = t[1], t[2] | |
log.trace("Add: %s (%s) ...", fileName, fullpath) | |
local ok, err = writer:write(fileName, file_reader(fullpath)) | |
if ok then log.info("Add: %s (%s) - %s", fileName, fullpath, tostring(ok)) | |
else | |
log.error("Add: %s (%s) - %s", fileName, fullpath, tostring(err)) | |
end | |
end | |
local ok, err = writer:close() | |
if ok then log.info("%s - done", oFile) | |
else | |
last_error, error_count = err, error_count + 1 | |
log.error("%s - %s", oFile, tostring(err)) | |
end | |
if error_count > 0 then return nil, last_error end | |
return true | |
end | |
end | |
----------------------------------------------------------- | |
BACKUP_DIR = BACKUP_DIR or path.currentdir() | |
local env = luasql.odbc() | |
local db, err = env:connect('fusionpbx', 'fusionpbx', PASSWORD) | |
if db then log.info("connected to database") | |
else | |
log.fatal("Can not connect to database: %s", tostring(err)) | |
os.exit(-1) | |
end | |
local function remove(filePath) | |
local ok, err = path.remove(filePath) | |
if ok then log.info("remove %s", filePath) | |
else log.error("remove %s - %s", filePath, tostring(err)) end | |
end | |
local function db_remove(sql, info) | |
local ok, err = db:execute(sql) | |
if ok then log.info("delete %s - %s", info, tostring(ok)) | |
else log.error("delete %s - %s (%s)", info, tostring(err), sql) end | |
end | |
local function clear_nginx_log(days) | |
local log_dir = J(NGINX_DIR, 'logs', '*.log') | |
walk_old_files(log_dir, days or 7, function(p) | |
local base = path.basename(p):lower() | |
if (base ~= 'error.log') and (base ~= 'access.log') then | |
remove(p) | |
end | |
end) | |
log_dir = J(NGINX_DIR, 'winsvc', 'logs', '*.log') | |
walk_old_files(log_dir, days or 7, function(p) | |
local base = path.basename(p):lower() | |
if base ~= 'ngx-service.log' then | |
remove(p) | |
end | |
end) | |
end | |
local function clear_php_log(days) | |
local log_dir = J(PHP_DIR, 'winsvc', 'logs', '*.log') | |
walk_old_files(log_dir, days or 7, function(p) | |
local base = path.basename(p):lower() | |
if base ~= 'php-service.log' then | |
remove(p) | |
end | |
end) | |
end | |
local function clear_log(days) | |
local log_dir = J(FS_DIR, 'log', 'freeswitch.log.*') | |
walk_old_files(log_dir, days or 7, function(p) | |
local base = path.basename(p):lower() | |
if base ~= 'freeswitch.log' then | |
remove(p) | |
end | |
end) | |
end | |
local function clear_fax(days) | |
walk_old_files('!' .. J(FS_DIR, 'storage', '*.tif'), days or 90, remove) | |
walk_old_files('!' .. J(FS_DIR, 'storage', '*.pdf'), days or 90, remove) | |
end | |
local function clear_wav(days) | |
walk_old_files('!' .. J(FS_DIR, 'storage', 'msg_*.wav'), days or 90, remove) | |
end | |
local function clear_rec(days) | |
walk_old_files('!' .. J(FS_DIR, 'recordings', '*.wav'), days or 90, function(p) | |
if string.find(p, '[\\/]archive[\\/]') then | |
remove(p) | |
end | |
end) | |
local counter = 1024 repeat | |
local flag = true | |
walk_empty_dirs(FS_DIR, function(p) | |
if not p:lower():find('[\\/]archive[\\/]') then return end | |
if path.basename(p):lower() == 'archive' then return end | |
flag = false | |
remove(p) | |
end) | |
counter = counter - 1 | |
until flag or counter <= 0 | |
end | |
local function clear_backup(days) | |
walk_old_files('!' .. J(BACKUP_DIR, '*.zip'), days or 7, remove) | |
walk_old_files('!' .. J(BACKUP_DIR, '*.sql'), days or 7, remove) | |
local counter = 100 repeat | |
local flag = true | |
walk_empty_dirs('!' .. BACKUP_DIR, function(p) | |
flag = false | |
remove(p) | |
end) | |
counter = counter - 1 | |
until flag or counter <= 0 | |
end | |
local function delete_fax(days) | |
local sql = ("delete from v_fax_files WHERE fax_date < NOW() + INTERVAL '%d days'") | |
:format(tonumber(days) or 90) | |
db_remove(sql, "Fax files") | |
end | |
local function delete_wav(days) | |
local sql = ("delete from v_voicemail_messages WHERE to_timestamp(created_epoch) < NOW() - INTERVAL '%d days'") | |
:format(tonumber(days) or 90) | |
db_remove(sql, "Voicemail messages") | |
end | |
local function delete_cdr(days) | |
local sql = ("delete from v_xml_cdr WHERE start_stamp < NOW() - INTERVAL '%d days'") | |
:format(tonumber(days) or 90) | |
db_remove(sql, "XML CDR") | |
end | |
local function delete_rec(days) | |
local sql = ("delete from v_call_recordings WHERE call_recording_date < NOW() - INTERVAL '%d days'") | |
:format(tonumber(days) or 90) | |
db_remove(sql, "CALL RECORDINGS") | |
end | |
local function backup_db(backup_path) | |
local cmd = 'pg_dump -F p --dbname=postgresql://fusionpbx' .. (PASSWORD and (':' .. PASSWORD) or '') .. '@127.0.0.1:5432/fusionpbx -f "' .. path.normalize(backup_path) .. '"' | |
local ok, status, msg = exec('.', cmd) | |
if msg then | |
local conv = iconv.new(LOG_CP, 'utf-8') | |
if conv then | |
msg = conv:iconv(msg) | |
end | |
end | |
if ok then log.info("backup db done - %s", tostring(msg)) | |
else log.error("backup db - %s", tostring(msg)) end | |
if ok and path.exists(backup_path) then | |
zip_dir(backup_path .. '.zip', backup_path) | |
path.remove(backup_path) | |
end | |
return ok, status, msg | |
end | |
clear_nginx_log(7) | |
clear_php_log(7) | |
clear_log(7) | |
clear_fax(90) | |
clear_wav(90) | |
clear_rec(90) | |
delete_wav(90) | |
delete_fax(90) | |
delete_cdr(90) | |
delete_rec(90) | |
local backup_dir = J(BACKUP_DIR, date():fmt("%F %H-%M-%S")) | |
path.mkdir(backup_dir) | |
backup_db(J(backup_dir, 'FusionPBX.sql')) | |
zip_dir (J(backup_dir, 'FreeSWITCH.zip'), J(FS_DIR, '*.*'), {filter = function(fullpath) | |
-- exclude call recordings from backup | |
if string.find(fullpath, [[\recordings\.-\archive]]) then return false end | |
return true | |
end, recurse = true, skipdirs = true}) | |
zip_dir (J(backup_dir, 'FusionPBX.zip'), J(FUSION_DIR, '*.*')) | |
clear_backup(7) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local path = require "path" | |
local sendmail = require "sendmail" | |
local ZipWriter = require "ZipWriter" | |
sendmail{ | |
server = { | |
address = "smtp.domain.local"; | |
user = "[email protected]"; | |
password = "***"; | |
ssl = {verify = {"none"}}; | |
}, | |
from = { | |
title = "Backup FusionPBX"; | |
address = "[email protected]"; | |
}, | |
to = { | |
title = "Dear Admin"; | |
address = "[email protected]"; | |
}, | |
message = { | |
"Backup FusionPBX", | |
file = { | |
source = ZipWriter.source(ZipWriter.new(), { | |
{"backup.log", path.fullpath("./logs/backup.log")} | |
}), | |
name = "backup.zip", | |
}, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment