NAME
App::FileSync::Transport::SSH - run the server over ssh
SYNOPSIS
my $transport = App::FileSync::Transport::SSH->new(
server => 'filesync@files.example.com', repo => 'docs', client => 'laptop',
identity => '~/.ssh/filesync_ed25519',
);
...
$transport->close or warn $transport->error_text;
METHODS
new
Purpose: start filesync-server on the server host for one session.
Args: server (user@host, host or user@[v6]), repo, client, optional role (default client), server_command (default filesync-server), host_key_checking (yes, the default, or accept-new), known_hosts, identity, port, command (the local ssh program), extra_args (arrayref of extra ssh arguments).
Returns: the transport.
Side Effects: runs ssh ... SERVER PROGRAM --repo R --client C --role X --proto 2. ssh's standard error goes to a temporary file, never the protocol pipe, and is available from "error_text". On Windows a .cmd or .bat ssh wrapper is started through COMSPEC, since CreateProcess cannot start one directly.
Usage:
my $transport = App::FileSync::Transport::SSH->new(%args);
EXAMPLE
my $t = App::FileSync::Transport::SSH->new(
server => 'backup', repo => 'photos', client => 'nas', role => 'replica', port => 2222,
);
print join(' ', @{ $t->command }), "\n";
API SPECIFICATION
INPUT
{
server => { type => 'string', matches => $SERVER },
repo => { type => 'string', min => 1, max => 64 },
client => { type => 'string', min => 1, max => 64 },
role => { type => 'string', optional => 1, memberof => [ 'client', 'replica' ] },
server_command => { type => 'string', optional => 1, matches => $PROGRAM },
host_key_checking => { type => 'string', optional => 1, memberof => [ 'yes', 'accept-new' ] },
known_hosts => { type => 'string', optional => 1 },
identity => { type => 'string', optional => 1 },
port => { type => 'integer', optional => 1, min => 1, max => 65535 },
command => { type => 'string', optional => 1 },
extra_args => { type => 'arrayref', optional => 1 },
}
OUTPUT
{ type => 'object', isa => 'App::FileSync::Transport::SSH' }
MESSAGES
Key Meaning Resolution
transport.missing server, repo or client absent fix the repo config
transport.bad_value a value is unsafe or out of range fix the repo config or settings
transport.bad_id the repo or client is not a plain id fix the repo config
io.write the error file cannot be created check the temp directory
FORMAL SPECIFICATION
pre server ∈ SERVER ∧ repo ∈ Id ∧ client ∈ Id ∧ role ∈ ROLES ∧ program ∈ PROGRAM
∧ (port = ⊥ ∨ 0 < port ≤ 65535) ∧ ∀ a : extra_args • a ∈ TEXT ∧ '\n' ¬in; a
argv = ⟨ssh⟩ ⁀ options ⁀ ⟨server, program, --repo, repo, --client, client, --role, role, --proto, PROTO⟩
PSEUDOCODE
validate every argument
build ssh's options: fixed ones, host key policy, known hosts, identity,
port, extra arguments
append the server, then the remote command
wrap a .cmd/.bat ssh in COMSPEC on Windows
start it with standard error going to a temporary file
reader
Documented with "exit_status" below.
writer
Documented with "exit_status" below.
command
Documented with "exit_status" below.
exit_status
Purpose: the session's pipes, the argv that was run, and ssh's exit status.
Args: none.
Returns: the read and write handles; a copy of the argv (for --verbose and tests); the exit status after "close" (undef before).
Side Effects: none.
Usage:
my $argv = $transport->command;
EXAMPLE
print "ran: @{ $transport->command }\n";
API SPECIFICATION
INPUT
[ { name => 'self', type => 'object' } ]
OUTPUT
{ type => 'scalar' }
MESSAGES
None.
FORMAL SPECIFICATION
command = copy(self.command) ; exit_status = self.status
close
Purpose: end the ssh session.
Args: none.
Returns: 1 when ssh exited 0, else 0. Repeated calls return 1.
Side Effects: closes both pipes, reaps ssh, records its exit status and signal, and reads what it wrote to standard error.
Usage:
$transport->close or warn $transport->error_text, "\n";
EXAMPLE
unless ($transport->close) {
printf "ssh exited %d: %s\n", $transport->exit_status, $transport->error_text;
}
API SPECIFICATION
INPUT
[ { name => 'self', type => 'object' } ]
OUTPUT
{ type => 'integer', memberof => [ 0, 1 ] }
MESSAGES
None.
FORMAL SPECIFICATION
close = 1 ⇔ closed ∨ exit(ssh) = 0
error_text
Purpose: what ssh or the remote shell said, for when the protocol failed.
Args: none.
Returns: the text, trimmed, with blank lines collapsed.
Side Effects: reads the error file if "close" has not yet.
Usage:
my $why = $transport->error_text;
EXAMPLE
print $transport->error_text, "\n"; # "Permission denied (publickey)."
API SPECIFICATION
INPUT
[ { name => 'self', type => 'object' } ]
OUTPUT
{ type => 'string' }
MESSAGES
None.
FORMAL SPECIFICATION
error_text = squeeze(trim(stderr))
valid_server
Documented with "valid_program" below.
valid_program
Purpose: check a server address and a remote program path.
Args: the value.
Returns: 1 or 0.
A server is user@host, host or user@[ipv6]; the user and host must start with a letter or digit, so neither can be read as an ssh option (a leading -) and nothing can split into another word. A program is a path of letters, digits, ., _, / and -, not starting with -.
Side Effects: none.
Usage:
die "bad server\n" unless App::FileSync::Transport::SSH::valid_server($server);
EXAMPLE
App::FileSync::Transport::SSH::valid_server('filesync@files.example.com'); # 1
App::FileSync::Transport::SSH::valid_server('-oProxyCommand=sh'); # 0
App::FileSync::Transport::SSH::valid_server('-v@host'); # 0
API SPECIFICATION
INPUT
[ { name => 'value', type => 'string', optional => 1 } ]
OUTPUT
{ type => 'integer', memberof => [ 0, 1 ] }
MESSAGES
None.
FORMAL SPECIFICATION
valid_server(v) ⇔ v ∈ SERVER
valid_program(v) ⇔ v ∈ PROGRAM
LIMITATIONS
extra_argsare passed to ssh unchecked apart from embedded NULs and newlines: they are the user's own options, and can therefore do anything ssh options can (-o ProxyCommand=...included). Only trusted configuration should set them.known_hostsandidentityare not tilde-expanded here; ssh expands~in-ibut not inUserKnownHostsFileon every version.
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
LICENCE AND COPYRIGHT
Copyright 2026 Nigel Horne.
Usage is subject to the GPL2 licence terms. If you use it, please let me know.