Rex-LibSSH
view release on metacpan or search on metacpan
.claude/agents/rex-libssh-test-writer.md view on Meta::CPAN
`AllowUsers` the current user, forked child with stdio to `/dev/null`, polled for up to
5s, `SIGTERM`+`waitpid` in `DESTROY`. Reuse it; do not add a second sshd bootstrap.
```perl
use lib 't/lib';
use TestSSHD;
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
set connection => 'LibSSH';
Rex::Config->set_private_key( $srv->client_key );
Rex::Config->set_public_key( $srv->client_key . '.pub' );
Rex::connect( server => $srv->host, port => $srv->port,
user => scalar getpwuid($<), private_key => $srv->client_key,
public_key => $srv->client_key . '.pub', auth_type => 'key' );
# ... assertions ...
Rex::pop_connection();
```
Note `CORE::open` / `CORE::close` in the existing integration test: `Rex::Commands::Fs`
exports `open`-shaped names into the file, so local filehandle work must be
`CORE::`-qualified. Same for `stat` â bare `stat` there is Rex's remote one, which is
the point.
.claude/skills/rex/SKILL.md view on Meta::CPAN
set connection => 'LibSSH';
# All Rex file operations now work without SFTP:
file '/etc/hostname', content => "myhost\n";
delete_lines_matching '/etc/fstab', matching => qr/\sswap\s/;
host_entry 'myhost.internal', ip => '127.0.1.1', aliases => ['myhost'];
```
Authentication:
```perl
Rex::Config->set_private_key('/root/.ssh/id_ed25519');
Rex::Config->set_public_key('/root/.ssh/id_ed25519.pub');
```
Host key checking is disabled by default (`strict_hostkeycheck => 0`).
## Getty's Rex distributions (CPAN)
### Rex::GPU (`Rex-GPU`)
```perl
- `Rex::Interface::Exec::LibSSH` â command execution via SSH exec channels
- `Rex::Interface::Fs::LibSSH` â `is_file`, `stat`, `ls`, etc. via exec
- `Rex::Interface::File::LibSSH` â file upload/download via `cat`/heredoc exec
## Key Details
- Host key verified against `known_hosts` by default; opt out per-connection
with `strict_hostkeycheck => 0` or Rexfile-wide with
`-feature => ['disable_strict_host_key_checking']` (CWE-322 fix, requires
Net::LibSSH >= 0.004)
- Public key auth via `Rex::Config->set_private_key` / `set_public_key`
- No SFTP subsystem needed on remote host
- Used by Rex::Rancher and Rex::GPU for Hetzner dedicated server deployments
## Dependencies
- `Net::LibSSH` (XS binding for libssh)
- `Alien::libssh` (provides the libssh C library, via Net::LibSSH)
- `Rex` (framework)
Consumed downstream by `Rex::GPU` and `Rex::Rancher`, which both
lib/Rex/Interface/Connection/LibSSH.pm view on Meta::CPAN
}
sub connect {
my ( $self, %opt ) = @_;
my $server = $opt{server};
my $port = $opt{port} || Rex::Config->get_port( server => $server ) || 22;
my $timeout = $opt{timeout} || Rex::Config->get_timeout( server => $server ) || 10;
my $user = $opt{user};
my $password = $opt{password};
my $privkey = $opt{private_key};
my $auth = $opt{auth_type} // 'key';
$self->{server} = $server;
$self->{is_sudo} = $opt{sudo};
$self->{__auth_info__} = \%opt;
( $server, $port ) = Rex::Helper::IP::get_server_and_port( $server, $port );
# Host key verification: a per-connect option wins, otherwise the same
# openssh_opt knobs the OpenSSH backend honours -- StrictHostKeyChecking
lib/Rex/LibSSH.pm view on Meta::CPAN
Rex's interface dispatch will automatically load
L<Rex::Interface::Connection::LibSSH>,
L<Rex::Interface::Exec::LibSSH>,
L<Rex::Interface::Fs::LibSSH>, and
L<Rex::Interface::File::LibSSH>.
=head2 Authentication
Supports public key authentication:
Rex::Config->set_private_key('/home/user/.ssh/id_ed25519');
Rex::Config->set_public_key('/home/user/.ssh/id_ed25519.pub');
Or pass keys directly to C<Rex::connect>:
Rex::connect(
server => '10.0.0.1',
user => 'root',
private_key => '/path/to/key',
public_key => '/path/to/key.pub',
auth_type => 'key',
);
=head2 Host key verification
The server's host key is verified against C<known_hosts> by default, exactly
like an interactive C<ssh> client but without the prompt: an unknown or
changed key makes the connection fail before any authentication is
attempted. Requires L<Net::LibSSH> 0.004 or later â earlier versions of
t/01-rex-integration.t view on Meta::CPAN
use Rex -feature => ['1.4'];
use Rex::Group::Entry::Server;
use Rex::Commands::Run;
use Rex::Commands::Fs;
use Rex::Commands::File;
use Rex::Config;
set connection => 'LibSSH';
Rex::Config->set_user( scalar getpwuid($<) );
Rex::Config->set_private_key( $srv->client_key );
Rex::Config->set_public_key( $srv->client_key . '.pub' );
Rex::connect(
server => $srv->host,
port => $srv->port,
user => scalar( getpwuid($<) ),
private_key => $srv->client_key,
public_key => $srv->client_key . '.pub',
auth_type => 'key',
knownhosts => $srv->known_hosts,
);
# --- run ---
my $out = run 'echo hello';
chomp $out;
is $out, 'hello', 'run echo works';
t/02-exec-signature.t view on Meta::CPAN
use Rex -feature => ['1.4'];
use Rex::Group::Entry::Server;
use Rex::Commands::Run;
use Rex::Commands::Fs;
use Rex::Config;
set connection => 'LibSSH';
Rex::Config->set_user( scalar getpwuid($<) );
Rex::Config->set_private_key( $srv->client_key );
Rex::Config->set_public_key( $srv->client_key . '.pub' );
Rex::connect(
server => $srv->host,
port => $srv->port,
user => scalar( getpwuid($<) ),
private_key => $srv->client_key,
public_key => $srv->client_key . '.pub',
auth_type => 'key',
knownhosts => $srv->known_hosts,
);
# ---------------------------------------------------------------------------
# 1. Env hash handover through the 4-arg exec($cmd, $path, $option) signature.
#
# Rex::Commands::Run::run invokes $exec->exec($cmd, $path, $option); the 2-arg
# form on Exec::Base bound $path to $option, so $option ended up undef and the
t/03-path-quoting.t view on Meta::CPAN
use Rex -feature => ['1.4'];
use Rex::Group::Entry::Server;
use Rex::Commands::Fs;
use Rex::Commands::File;
use Rex::Config;
set connection => 'LibSSH';
Rex::Config->set_user( scalar getpwuid($<) );
Rex::Config->set_private_key( $srv->client_key );
Rex::Config->set_public_key( $srv->client_key . '.pub' );
Rex::connect(
server => $srv->host,
port => $srv->port,
user => scalar( getpwuid($<) ),
private_key => $srv->client_key,
public_key => $srv->client_key . '.pub',
auth_type => 'key',
knownhosts => $srv->known_hosts,
);
# Use a fresh tempdir on the remote side (the sshd is local, so a
# local /tmp path is visible to the remote user too).
my $dir = tempdir(CLEANUP => 1);
# Local source file for upload(). All "remote file creation" goes
t/04-no-sftp.t view on Meta::CPAN
use Rex -feature => ['1.4'];
use Rex::Group::Entry::Server;
use Rex::Commands::Run;
use Rex::Commands::Fs;
use Rex::Commands::File;
use Rex::Config;
set connection => 'LibSSH';
Rex::Config->set_user( scalar getpwuid($<) );
Rex::Config->set_private_key( $srv->client_key );
Rex::Config->set_public_key( $srv->client_key . '.pub' );
Rex::connect(
server => $srv->host,
port => $srv->port,
user => scalar( getpwuid($<) ),
private_key => $srv->client_key,
public_key => $srv->client_key . '.pub',
auth_type => 'key',
knownhosts => $srv->known_hosts,
);
# Every Fs/File operation must work via SSH exec channels alone. On a host
# without an SFTP subsystem, the OpenSSH/SSH backends crash with
# "Can't call method \"stat\" on an undefined value". LibSSH should sail
# through, and that is the contract this test pins down.
t/05-hostkey.t view on Meta::CPAN
use Rex -feature => ['1.4'];
use Rex::Group::Entry::Server;
use Rex::Commands::Run;
use Rex::Config;
use Rex::Interface::Connection;
use Rex::TaskList;
set connection => 'LibSSH';
Rex::Config->set_user( scalar getpwuid($<) );
Rex::Config->set_private_key( $srv->client_key );
Rex::Config->set_public_key( $srv->client_key . '.pub' );
sub with_timeout {
my ( $seconds, $code ) = @_;
my $result;
local $@;
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm($seconds);
$result = $code->();
t/05-hostkey.t view on Meta::CPAN
system( 'ssh-keygen', '-t', 'ed25519', '-N', '', '-f', "$dir/key", '-q' ) == 0
or die "ssh-keygen failed";
return "$dir/key.pub";
}
sub base_opts {
return (
server => $srv->host,
port => $srv->port,
user => scalar getpwuid($<),
private_key => $srv->client_key,
public_key => $srv->client_key . '.pub',
auth_type => 'key',
);
}
# Rex::connect dies before push_connection when is_connected is false, so
# there is nothing to pop in the normal "refused" case below. This is a
# defensive net around that, not the expected path: pop only if a
# connection was actually pushed, i.e. either Rex::connect unexpectedly
# succeeded (which would mean a refusal test regressed) or it died after
t/05-hostkey.t view on Meta::CPAN
my $task_output;
task 'hostkey_task_path_test', sub {
$task_output = run 'echo task-path-ok';
};
my $server = Rex::Group::Entry::Server->new(
name => $srv->host,
port => $srv->port,
user => scalar getpwuid($<),
private_key => $srv->client_key,
public_key => $srv->client_key . '.pub',
auth_type => 'key',
);
my $task = Rex::TaskList->create()->get_task('hostkey_task_path_test');
eval { $task->run($server) };
my $err = $@;
ok !$err,
'task path: Rex::Task->run() against an unknown host key succeeds via the openssh_opt opt-out'
or diag "task run died: $err";
( run in 0.621 second using v1.01-cache-2.11-cpan-036bef1c656 )