lsyncd/default-rsyncssh.lua

515 lines
9.0 KiB
Lua
Raw Normal View History

--
-- default-rsyncssh.lua
--
-- Improved rsync - sync with rsync, but moves and deletes executed over ssh.
-- A (Layer 1) configuration.
--
-- Note:
2012-02-15 19:10:50 +00:00
-- this is infact just a configuration using Layer 1 configuration
-- like any other. It only gets compiled into the binary by default.
-- You can simply use a modified one, by copying everything into a
2012-02-15 19:10:50 +00:00
-- config file of yours and name it differently.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--
2016-12-01 13:12:56 +00:00
if not default
then
error( 'default not loaded' );
end
2016-12-01 13:12:56 +00:00
if not default.rsync
then
error( 'default.rsync not loaded' );
end
2016-12-01 13:12:56 +00:00
if default.rsyncssh
then
error( 'default-rsyncssh already loaded' );
end
--
-- rsyncssh extends default.rsync
--
local rsyncssh = { default.rsync }
2016-12-01 13:12:56 +00:00
default.rsyncssh = rsyncssh
--
-- used to ensure there aren't typos in the keys
--
rsyncssh.checkgauge = {
-- unsets the inherited value of from default.rsync
2012-10-06 12:22:08 +00:00
target = false,
onMove = true,
-- rsyncssh users host and targetdir
2012-10-06 12:22:08 +00:00
host = true,
targetdir = true,
sshExitCodes = true,
rsyncExitCodes = true,
-- ssh settings
ssh = {
binary = true,
identityFile = true,
options = true,
port = true,
_extra = true
},
-- xargs settings
xargs = {
2012-10-06 12:22:08 +00:00
binary = true,
delimiter = true,
_extra = true
}
}
--
-- Spawns rsync for a list of events
--
rsyncssh.action = function( inlet )
local event, event2 = inlet.getEvent( )
local config = inlet.getConfig( )
-- makes move local on target host
-- if the move fails, it deletes the source
if event.etype == 'Move' then
local path1 = config.targetdir .. event.path
local path2 = config.targetdir .. event2.path
path1 = "'" .. path1:gsub ('\'', '\'"\'"\'') .. "'"
path2 = "'" .. path2:gsub ('\'', '\'"\'"\'') .. "'"
log(
'Normal',
'Moving ',
event.path,
' -> ',
event2.path
)
spawn(
event,
config.ssh.binary,
config.ssh._computed,
config.host,
'mv',
path1,
path2,
'||', 'rm', '-rf',
path1
)
return
end
-- uses ssh to delete files on remote host
-- instead of constructing rsync filters
if event.etype == 'Delete'
then
if config.delete ~= true
and config.delete ~= 'running'
then
inlet.discardEvent( event )
return
end
-- gets all other deletes ready to be
-- executed
local elist = inlet.getEvents(
function( e )
return e.etype == 'Delete'
end
)
-- returns the paths of the delete list
local paths = elist.getPaths(
function( etype, path1, path2 )
if path2
then
return config.targetdir..path1, config.targetdir..path2
else
return config.targetdir..path1
end
end
)
-- ensures none of the paths is '/'
for _, v in pairs( paths )
do
if string.match( v, '^%s*/+%s*$' )
then
log( 'Error', 'cowardly refusing to `rm -rf /` the target!' )
terminate( -1 ) -- ERRNO
end
end
log(
'Normal',
'Deleting list\n',
table.concat( paths, '\n' )
)
local params = { }
spawn(
elist,
config.ssh.binary,
'<', table.concat(paths, config.xargs.delimiter),
params,
config.ssh._computed,
config.host,
config.xargs.binary,
config.xargs._extra
)
return
end
--
-- for everything else a rsync is spawned
--
local elist = inlet.getEvents(
function( e )
-- TODO use a table
return e.etype ~= 'Move' and
e.etype ~= 'Delete' and
e.etype ~= 'Init' and
e.etype ~= 'Blanket'
end
)
local paths = elist.getPaths( )
--
-- removes trailing slashes from dirs.
--
for k, v in ipairs( paths ) do
if string.byte( v, -1 ) == 47 then
paths[k] = string.sub( v, 1, -2 )
end
end
local sPaths = table.concat( paths, '\n' )
local zPaths = table.concat( paths, '\000' )
log(
'Normal',
'Rsyncing list\n',
sPaths
)
spawn(
elist,
config.rsync.binary,
'<', zPaths,
config.rsync._computed,
'--from0',
'--files-from=-',
config.source,
config.host .. ':' .. config.targetdir
)
end
-----
-- Called when collecting a finished child process
--
rsyncssh.collect = function( agent, exitcode )
local config = agent.config
if not agent.isList and agent.etype == 'Init' then
local rc = config.rsyncExitCodes[exitcode]
if rc == 'ok' then
log('Normal', 'Startup of "',agent.source,'" finished: ', exitcode)
elseif rc == 'again' then
if settings('insist') then
log('Normal', 'Retrying startup of "',agent.source,'": ', exitcode)
else
log('Error', 'Temporary or permanent failure on startup of "',
agent.source, '". Terminating since "insist" is not set.');
terminate(-1) -- ERRNO
end
elseif rc == 'die' then
log('Error', 'Failure on startup of "',agent.source,'": ', exitcode)
else
log('Error', 'Unknown exitcode on startup of "', agent.source,': "',exitcode)
rc = 'die'
end
return rc
end
if agent.isList then
local rc = config.rsyncExitCodes[exitcode]
if rc == 'ok' then
log('Normal', 'Finished (list): ',exitcode)
elseif rc == 'again' then
log('Normal', 'Retrying (list): ',exitcode)
elseif rc == 'die' then
log('Error', 'Failure (list): ', exitcode)
else
log('Error', 'Unknown exitcode (list): ',exitcode)
rc = 'die'
end
return rc
else
local rc = config.sshExitCodes[exitcode]
if rc == 'ok' then
log('Normal', 'Finished ',agent.etype,' ',agent.sourcePath,': ',exitcode)
elseif rc == 'again' then
log('Normal', 'Retrying ',agent.etype,' ',agent.sourcePath,': ',exitcode)
elseif rc == 'die' then
log('Normal', 'Failure ',agent.etype,' ',agent.sourcePath,': ',exitcode)
else
log('Error', 'Unknown exitcode ',agent.etype,' ',agent.sourcePath,': ',exitcode)
rc = 'die'
end
return rc
end
end
--
-- checks the configuration.
--
2012-10-06 12:22:08 +00:00
rsyncssh.prepare = function( config, level )
2012-10-06 12:22:08 +00:00
default.rsync.prepare( config, level + 1, true )
2016-11-25 13:55:59 +00:00
if not config.host
then
error(
'default.rsyncssh needs "host" configured',
level
)
end
2016-11-25 13:55:59 +00:00
if not config.targetdir
then
error(
'default.rsyncssh needs "targetdir" configured',
level
)
end
--
-- computes the ssh options
--
2016-11-25 13:55:59 +00:00
if config.ssh._computed
then
error(
'please do not use the internal rsync._computed parameter',
level
)
end
2016-11-25 13:55:59 +00:00
local cssh = config.ssh;
2016-11-25 13:55:59 +00:00
cssh._computed = { }
2016-11-25 13:55:59 +00:00
local computed = cssh._computed
2016-11-25 13:55:59 +00:00
local computedN = 1
2016-11-25 13:55:59 +00:00
local rsyncc = config.rsync._computed
2016-11-25 13:55:59 +00:00
if cssh.identityFile
then
computed[ computedN ] = '-i'
2016-11-25 13:55:59 +00:00
computed[ computedN + 1 ] = cssh.identityFile
2016-11-25 13:55:59 +00:00
computedN = computedN + 2
2016-11-25 13:55:59 +00:00
if not config.rsync._rshIndex
then
config.rsync._rshIndex = #rsyncc + 1
2016-11-25 13:55:59 +00:00
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
end
rsyncc[ config.rsync._rshIndex ] =
rsyncc[ config.rsync._rshIndex ] ..
' -i ' ..
cssh.identityFile
end
2016-11-25 13:55:59 +00:00
if cssh.options
then
for k, v in pairs( cssh.options )
do
computed[ computedN ] = '-o'
2016-11-25 13:55:59 +00:00
computed[ computedN + 1 ] = k .. '=' .. v
2016-11-25 13:55:59 +00:00
computedN = computedN + 2
2016-11-25 13:55:59 +00:00
if not config.rsync._rshIndex
then
config.rsync._rshIndex = #rsyncc + 1
2016-11-25 13:55:59 +00:00
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
end
rsyncc[ config.rsync._rshIndex ] =
table.concat(
{
rsyncc[ config.rsync._rshIndex ],
' -o ',
k,
'=',
v
},
''
)
end
end
2016-11-25 13:55:59 +00:00
if cssh.port
then
computed[ computedN ] = '-p'
2016-11-25 13:55:59 +00:00
computed[ computedN + 1 ] = cssh.port
2016-11-25 13:55:59 +00:00
computedN = computedN + 2
2016-11-25 13:55:59 +00:00
if not config.rsync._rshIndex
then
config.rsync._rshIndex = #rsyncc + 1
2016-11-25 13:55:59 +00:00
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
end
rsyncc[ config.rsync._rshIndex ] =
rsyncc[ config.rsync._rshIndex ] .. ' -p ' .. cssh.port
end
2016-11-25 13:55:59 +00:00
if cssh._extra
then
for k, v in ipairs( cssh._extra )
do
computed[ computedN ] = v
2016-11-25 13:55:59 +00:00
computedN = computedN + 1
end
end
-- appends a slash to the targetdir if missing
2016-11-25 13:55:59 +00:00
if string.sub( config.targetdir, -1 ) ~= '/'
then
config.targetdir =
config.targetdir .. '/'
end
end
--
-- allow processes
--
2016-08-29 11:12:09 +00:00
rsyncssh.maxProcesses = 1
--
-- The core should not split move events
--
2016-08-29 11:12:09 +00:00
rsyncssh.onMove = true
--
-- default delay
--
2016-08-29 11:12:09 +00:00
rsyncssh.delay = 15
--
-- no default exit codes
--
2016-08-29 11:12:09 +00:00
rsyncssh.exitcodes = false
--
-- rsync exit codes
--
2016-08-29 11:12:09 +00:00
rsyncssh.rsyncExitCodes = default.rsyncExitCodes
--
-- ssh exit codes
--
2016-08-29 11:12:09 +00:00
rsyncssh.sshExitCodes = default.sshExitCodes
--
-- xargs calls configuration
--
-- xargs is used to delete multiple remote files, when ssh access is
-- available this is simpler than to build filters for rsync for this.
--
rsyncssh.xargs = {
--
-- the binary called (on target host)
binary =
'/usr/bin/xargs',
--
-- delimiter, uses null by default, you might want to override this for older
-- by for example '\n'
delimiter =
'\000',
--
-- extra parameters
_extra =
{ '-0', 'rm -rf' }
}
--
-- ssh calls configuration
--
-- ssh is used to move and delete files on the target host
--
rsyncssh.ssh = {
--
-- the binary called
--
binary =
'/usr/bin/ssh',
--
-- if set adds this key to ssh
--
identityFile =
nil,
--
-- if set adds this special options to ssh
--
options =
nil,
--
-- if set connect to this port
--
port =
nil,
--
-- extra parameters
--
_extra =
{ }
}