Skip to content

Instantly share code, notes, and snippets.

@moteus
Last active November 2, 2017 07:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save moteus/6574437c2374a4abde7b to your computer and use it in GitHub Desktop.
Save moteus/6574437c2374a4abde7b to your computer and use it in GitHub Desktop.
Backup FusionPBX
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)
local path = require "path"
local sendmail = require "sendmail"
local ZipWriter = require "ZipWriter"
sendmail{
server = {
address = "smtp.domain.local";
user = "pbx@domain.local";
password = "***";
ssl = {verify = {"none"}};
},
from = {
title = "Backup FusionPBX";
address = "pbx@domain.local";
},
to = {
title = "Dear Admin";
address = "admin@domain.local";
},
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