mirror of
https://github.com/pragma-/pbot.git
synced 2024-11-25 21:39:27 +01:00
17b69f04ff
* replace heartbeat with health-check * instead of steady stream of newlines every 5s, now awaits input and responds with `vmstat` output * more reliably use host/config/vm-exec.json to get libvirt domain name for snapshot-revert, server address, serial ports, vagrant setting, etc * use iptables/nftables to disable networking * added guest/bin/disable-network-[iptables,nftables] * added guest/bin/enable-network-[iptables,nftables] * replace ugly ___OUTPUT___ texts in sh, bash, ksh, zsh languages * documentation updates and tweaks
178 lines
4.2 KiB
Perl
178 lines
4.2 KiB
Perl
#!/usr/bin/env perl
|
|
|
|
# File: Guest.pm
|
|
#
|
|
# Purpose: Collection of functions to interface with the PBot VM Guest and
|
|
# execute VM commands.
|
|
|
|
# SPDX-FileCopyrightText: 2022-2024 Pragmatic Software <pragma78@gmail.com>
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
package Guest;
|
|
|
|
use 5.020;
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
use feature qw/signatures/;
|
|
no warnings qw(experimental::signatures);
|
|
|
|
use English;
|
|
use Encode;
|
|
use File::Basename;
|
|
use JSON::XS;
|
|
use Data::Dumper;
|
|
|
|
sub read_input($input, $buffer, $tag) {
|
|
my $line;
|
|
my $total_read = 0;
|
|
|
|
print STDERR "$tag waiting for input...\n";
|
|
my $ret = sysread($input, my $buf, 16384);
|
|
|
|
if (not defined $ret) {
|
|
print STDERR "Error reading $tag: $!\n";
|
|
return undef;
|
|
}
|
|
|
|
if ($ret == 0) {
|
|
print STDERR "$tag input closed.\n";
|
|
return 0;
|
|
}
|
|
|
|
$total_read += $ret;
|
|
|
|
print STDERR "$tag read $ret bytes [$total_read total] [$buf]\n";
|
|
|
|
$$buffer .= $buf;
|
|
|
|
return undef if $$buffer !~ s/\s*:end:\s*$//m;
|
|
|
|
$line = $$buffer;
|
|
chomp $line;
|
|
|
|
$$buffer = '';
|
|
$total_read = 0;
|
|
|
|
print STDERR "-" x 40, "\n";
|
|
print STDERR "$tag got [$line]\n";
|
|
|
|
my $command = eval { decode_json($line) };
|
|
|
|
if ($@) {
|
|
print STDERR "Failed to decode JSON: $@\n";
|
|
return undef;
|
|
}
|
|
|
|
$command->{arguments} //= '';
|
|
$command->{input} //= '';
|
|
|
|
print STDERR Dumper($command), "\n";
|
|
|
|
return $command;
|
|
}
|
|
|
|
sub process_command($command, $mod, $user, $tag) {
|
|
my ($uid, $gid, $home) = (getpwnam $user)[2, 3, 7];
|
|
|
|
if (not $uid and not $gid) {
|
|
print STDERR "Could not find user $user: $!\n";
|
|
return undef;
|
|
}
|
|
|
|
my $pid = fork;
|
|
|
|
if (not defined $pid) {
|
|
print STDERR "process_command: fork failed: $!\n";
|
|
return undef;
|
|
}
|
|
|
|
if ($pid == 0) {
|
|
if ($command->{'persist-key'}) {
|
|
system ("rm -rf \"/home/$user/$command->{'persist-key'}\" 1>&2");
|
|
system("mount /dev/vdb1 /root/factdata 1>&2");
|
|
system("mkdir -p \"/root/factdata/$command->{'persist-key'}\" 1>&2");
|
|
system("cp -R -p \"/root/factdata/$command->{'persist-key'}\" \"/home/$user/$command->{'persist-key'}\" 1>&2");
|
|
}
|
|
|
|
my $dir = "/home/$user/$$";
|
|
|
|
system("mkdir -p $dir 1>&2");
|
|
|
|
system("chmod -R 755 $dir 1>&2");
|
|
system("chown -R $user $dir 1>&2");
|
|
system("chgrp -R $user $dir 1>&2");
|
|
system("pkill -u $user 1>&2");
|
|
|
|
system("date -s \@$command->{date} 1>&2");
|
|
|
|
$ENV{USER} = $user;
|
|
$ENV{LOGNAME} = $user;
|
|
$ENV{HOME} = $home;
|
|
|
|
chdir("/home/$user/$$");
|
|
|
|
$GID = $gid;
|
|
$EGID = "$gid $gid";
|
|
$EUID = $UID = $uid;
|
|
|
|
my $result = run_command($command, $mod);
|
|
|
|
print STDERR "=" x 40, "\n";
|
|
|
|
# ensure output is newline-terminated
|
|
$result .= "\n" unless $result =~ /\n$/;
|
|
|
|
return $result;
|
|
} else {
|
|
# wait for child to finish
|
|
waitpid($pid, 0);
|
|
|
|
# clean up persistent factoid storage
|
|
if ($command->{'persist-key'}) {
|
|
system("cp -R -p \"/home/$user/$command->{'persist-key'}\" \"/root/factdata/$command->{'persist-key'}\"");
|
|
system("umount /root/factdata");
|
|
system ("rm -rf \"/home/$user/$command->{'persist-key'}\"");
|
|
}
|
|
|
|
# kill any left-over processes started by $user
|
|
system("pkill -u $user");
|
|
system("rm -rf /home/$user/$pid");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
sub run_command($command, $mod) {
|
|
local $SIG{CHLD} = 'DEFAULT';
|
|
|
|
$mod->preprocess;
|
|
$mod->postprocess if not $mod->{error} and not $mod->{done};
|
|
|
|
if (exists $mod->{no_output} or not length $mod->{output}) {
|
|
if ($command->{factoid}) {
|
|
$mod->{output} = '';
|
|
} else {
|
|
$mod->{output} .= "\n" if length $mod->{output};
|
|
|
|
if (not $mod->{error}) {
|
|
$mod->{output} .= "Success (no output).\n";
|
|
} else {
|
|
$mod->{output} .= "Exit $mod->{error}.\n";
|
|
}
|
|
}
|
|
} elsif ($mod->{error}) {
|
|
$mod->{output} .= " [Exit $mod->{error}]";
|
|
}
|
|
|
|
return $mod->{output};
|
|
}
|
|
|
|
sub send_output($output, $result, $tag) {
|
|
my $json = encode_json({ result => $result });
|
|
print $output "result:$json\n";
|
|
print $output "result:end\n";
|
|
}
|
|
|
|
1;
|