pbot-vm: massive refactor

* 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
This commit is contained in:
Pragmatic Software 2024-04-10 01:21:18 -07:00
parent 5d3f188a09
commit 17b69f04ff
No known key found for this signature in database
GPG Key ID: CC916B6E3C84ECCE
24 changed files with 343 additions and 208 deletions

View File

@ -0,0 +1,9 @@
#!/bin/sh
# disables all incoming, outgoing and forwarded traffic except incoming/established SSH
iptables -F
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P OUTPUT DROP

View File

@ -0,0 +1,8 @@
#!/bin/sh
# disables all incoming, outgoing and forwarded traffic except incoming/established SSH
nft add table ip filter
nft add chain ip filter INPUT '{ type filter hook input priority 0; policy drop; }'
nft add chain ip filter OUTPUT '{ type filter hook output priority 0; policy drop; }'
nft 'add rule ip filter INPUT ct state related,established counter accept'
nft 'add rule ip filter INPUT tcp dport 22 counter accept'
nft 'add rule ip filter OUTPUT ct state related,established counter accept'

View File

@ -0,0 +1,3 @@
#!/bin/sh
# removes all iptables rules to re-enable networking
iptables -F

View File

@ -0,0 +1,3 @@
#!/bin/sh
# deletes filter table to re-enable networking
nft delete table ip filter

View File

@ -5,7 +5,7 @@
# Purpose: PBot VM Guest server. Runs inside PBot VM Guest and processes
# incoming VM commands from vm-exec.
# SPDX-FileCopyrightText: 2022 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2022-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use 5.020;
@ -20,7 +20,7 @@ use constant {
USERNAME => 'vm',
MOD_DIR => '/usr/local/share/pbot-vm/',
SERIAL => '/dev/ttyS2',
HEARTBEAT => '/dev/ttyS3',
HEALTH => '/dev/ttyS3',
VPORT => $ENV{PBOTVM_VPORT} // 5555,
};
@ -30,7 +30,6 @@ use lib MOD_DIR . "Languages";
use Guest;
use File::Basename;
use IPC::Shareable;
my %languages;
@ -68,11 +67,9 @@ sub serial_server() {
open(my $input, '<', SERIAL) or die $!;
open(my $output, '>', SERIAL) or die $!;
tie my $running, 'IPC::Shareable', { key => 'running' };
my $buffer = '';
while ($running) {
while (1) {
my $command = Guest::read_input($input, \$buffer, 'Serial');
if (not defined $command) {
@ -119,19 +116,20 @@ sub do_server() {
}
}
sub do_heartbeat() {
open(my $heartbeat, '>', HEARTBEAT) or die $!;
sub do_healthcheck() {
open(my $health_in, '<', HEALTH) or die $!;
open(my $health_out, '>', HEALTH) or die $!;
tie my $running, 'IPC::Shareable', { key => 'running' };
print "Healthcheck listening on PID $$...\n";
print "Heart beating on PID $$...\n";
while ($running) {
print $heartbeat "\n";
sleep 5;
while (1) {
my $input = <$health_in>;
my $vmstat = `vmstat`;
print $health_out "$vmstat\n";
print $health_out ":END\n";
}
print "Heart beat stopped.\n";
print "Healthcheck stopped.\n";
exit; # exit child process
}
@ -160,14 +158,10 @@ sub main() {
install_signal_handlers();
tie my $running, 'IPC::Shareable', { key => 'running', create => 1, destroy => 1 };
$running = 1;
my $pid = fork // die "Fork failed: $!";
if ($pid == 0) {
do_heartbeat();
do_healthcheck();
} else {
do_server();
}

View File

@ -1,11 +1,11 @@
#!/bin/sh
#!/bin/bash
# File: setup-guest
#
# Purpose: Sets up PBot VM Guest. Copies necessary files to the appropriate
# location, sets up environment variables and various configuration details.
# SPDX-FileCopyrightText: 2022 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2022-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
# determine OS/distribution
@ -56,9 +56,6 @@ cp guest/include/prelude.h /usr/include
# require root password for polkit actions
cp guest/polkit/* /etc/polkit-1/rules.d/
# disable networking
nmcli networking off
# set environment variables
if ! grep -qF "pbot-vm" /root/.bashrc; then
echo '# pbot-vm' >> /root/.bashrc
@ -66,8 +63,12 @@ if ! grep -qF "pbot-vm" /root/.bashrc; then
echo export ASAN_OPTIONS=detect_leaks=0 >> /root/.bashrc
fi
echo PBot Guest VM is now set up.
export DEBUGINFOD_URLS
export ASAN_OPTIONS=detect_leaks=0
echo PBot Guest VM is set up.
echo
echo !! Networking is now disabled. To re-enable networking run: nmcli networking on
echo
echo For changes to take effect, run this command now: source /root/.bashrc
echo To start PBot Guest Server: guest-server
# make environment variables take effect
exec /bin/bash

View File

@ -5,7 +5,7 @@
# Purpose: Collection of functions to interface with the PBot VM Guest and
# execute VM commands.
# SPDX-FileCopyrightText: 2022 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2022-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package Guest;
@ -22,7 +22,6 @@ use English;
use Encode;
use File::Basename;
use JSON::XS;
use IPC::Shareable;
use Data::Dumper;
sub read_input($input, $buffer, $tag) {
@ -59,7 +58,13 @@ sub read_input($input, $buffer, $tag) {
print STDERR "-" x 40, "\n";
print STDERR "$tag got [$line]\n";
my $command = decode_json($line);
my $command = eval { decode_json($line) };
if ($@) {
print STDERR "Failed to decode JSON: $@\n";
return undef;
}
$command->{arguments} //= '';
$command->{input} //= '';

View File

@ -16,10 +16,17 @@ apt install -y socat
# for `cc` C language support
apt install -y libubsan1 libasan8 gdb gcc gcc-multilib clang
# for pbot-vm guest-server support
apt install -y --no-install-recommends libipc-shareable-perl libipc-run-perl libjson-xs-perl
# for `cc` additional languages
apt install -y ksh zsh tcl lua5.4 php8.2-cli nodejs guile3.0 beef bc g++
apt install -y clisp golang-go
apt install -y --no-install-recommends default-jre default-jdk
# for pbot-vm guest-server support
apt install -y --no-install-recommends libipc-run-perl libjson-xs-perl
# disable networking
./guest/bin/disable-network-iptables
echo 'Networking disabled.'
echo 'To re-enable, run ./guest/bin/enable-networking-iptables'
echo 'To disable again, run ./guest/bin/disable-network-iptables'

View File

@ -14,10 +14,16 @@ zypper -n in socat
# for `cc` C language support
zypper -n in libubsan1 libasan8 gdb gcc gcc-32bit glibc-32bit clang
# for pbot-vm guest-server support
zypper -n in perl-IPC-Run perl-JSON-XS make cpanm
cpanm -n IPC::Shareable
# for `cc` additional languages
zypper -n in ksh zsh tcl lua php8-cli nodejs-common guile bff bc gcc-c++
zypper -n in --no-recommends clisp gcc-go java java-devel
# for pbot-vm guest-server support
zypper -n in perl-IPC-Run perl-JSON-XS
# disable networking
./guest/bin/disable-network-nftables
echo 'Networking disabled.'
echo 'To re-enable, run ./guest/bin/enable-networking-nftables'
echo 'To disable again, run ./guest/bin/disable-network-nftables'

View File

@ -2,15 +2,18 @@
# File: vm-exec
#
# Purpose: Process and send commands to the PBot Guest server (guest-server) on
# the default VM socket CID/port (7/5555) or the default serial TCP port (5555).
# Purpose: Process and send commands to the PBot Guest server (guest-server)
# using the details from the config/vm-exec.json configuration file.
#
# Additionally, takes `-revert` and `-health` options to revert VM or check
# VM's health.
#
# Use the PBOTVM_CID, PBOTVM_VPORT and/or PBOTVM_SERIAL environment variables to
# override these defaults. E.g.:
# override the config/vm-exec.json values. E.g.:
#
# $ PBOTVM_CID=42 PBOTVM_SERIAL=7777 vm-exec -lang=sh echo test
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2021-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use 5.020;
@ -22,9 +25,13 @@ use feature qw(signatures);
no warnings qw(experimental::signatures);
use constant {
SERIAL => $ENV{PBOTVM_SERIAL} // 5555,
CID => $ENV{PBOTVM_CID} // 7,
VPORT => $ENV{PBOTVM_VPORT} // 5555,
DOMAIN => $ENV{PBOTVM_DOMAIN} // 'pbot-vm',
ADDR => $ENV{PBOTVM_ADDR} // '127.0.0.1',
SERIAL => $ENV{PBOTVM_SERIAL} // 5555,
HEALTH => $ENV{PBOTVM_HEALTH} // 5556,
CID => $ENV{PBOTVM_CID} // 7,
VPORT => $ENV{PBOTVM_VPORT} // 5555,
VAGRANT => $ENV{PBOTVM_VAGRANT} // 0,
};
use File::Basename;
@ -64,7 +71,7 @@ sub connect_serial($context) {
print STDERR "Connecting to remote VM serial port $context->{'vm-serial'}\n";
my $vm = IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerAddr => $context->{'vm-addr'},
PeerPort => $context->{'vm-serial'},
Proto => 'tcp',
Type => SOCK_STREAM
@ -96,6 +103,12 @@ sub connect_vm($context) {
sub make_context_from_args(@args_in) {
my $args = join ' ', @args_in;
# extract leading options
my %opts;
while ($args =~ s/^-(revert|health)\s+//) {
$opts{$1} = 1;
}
my $context = eval { decode_json $args };
if ($@) {
@ -108,8 +121,14 @@ sub make_context_from_args(@args_in) {
}
}
# set extracted leading options
foreach my $opt (keys %opts) {
print STDERR "Setting option `$opt`.\n";
$context->{$opt} = 1;
}
# parse options specific to vm-exec
while ($context->{code} =~ s/^-(lang|vm-cid|vm-vport|vm-serial|vm)=([^ ]+)\s*//) {
while ($context->{code} =~ s/^-(lang|revert|health|vm-domain|vm-health|vm-cid|vm-vport|vm-serial|vm)=([^ ]+)\s*//) {
my ($option, $value) = ($1, $2);
print STDERR "Overriding `$option` to `$value`.\n";
$context->{$option} = lc $value;
@ -190,13 +209,17 @@ sub configure_context($context, $config) {
if (not defined $entry) {
my $machines = list_machines($config);
print "Unknown machine '$machine'; available machines are: $machines\n";
exit 1;
exit 3;
}
# override values
$context->{'vm-serial'} = $entry->{'serial'};
$context->{'vm-cid'} = $entry->{'cid'};
$context->{'vm-vport'} = $entry->{'vport'};
$context->{'vm-domain'} = $machine;
$context->{'vm-addr'} = $entry->{'addr'};
$context->{'vm-health'} = $entry->{'health'};
$context->{'vm-serial'} = $entry->{'serial'};
$context->{'vm-cid'} = $entry->{'cid'};
$context->{'vm-vport'} = $entry->{'vport'};
$context->{'vm-vagrant'} = $entry->{'vagrant'};
} else {
# otherwise configure any undefined values as default machine
my $machine = $config->{'default-machine'};
@ -207,22 +230,30 @@ sub configure_context($context, $config) {
if (not defined $entry) {
my $machines = list_machines($config);
print "Unknown machine '$machine'; available machines are: $machines\n";
exit 1;
exit 3;
}
# update any undefined values, preserving any existing values
$context->{'vm-serial'} //= $entry->{'serial'};
$context->{'vm-cid'} //= $entry->{'cid'};
$context->{'vm-vport'} //= $entry->{'vport'};
$context->{'vm-domain'} //= $machine;
$context->{'vm-addr'} //= $entry->{'addr'};
$context->{'vm-health'} //= $entry->{'health'};
$context->{'vm-serial'} //= $entry->{'serial'};
$context->{'vm-cid'} //= $entry->{'cid'};
$context->{'vm-vport'} //= $entry->{'vport'};
$context->{'vm-vagrant'} //= $entry->{'vagrant'};
}
# set any undefined values to default values
$context->{nick} //= 'vm';
$context->{channel} //= 'vm';
$context->{lang} //= 'c2x';
$context->{'vm-serial'} //= SERIAL;
$context->{'vm-cid'} //= CID;
$context->{'vm-vport'} //= VPORT;
$context->{nick} //= 'vm';
$context->{channel} //= 'vm';
$context->{lang} //= 'c2x';
$context->{'vm-domain'} //= DOMAIN;
$context->{'vm-addr'} //= ADDR;
$context->{'vm-health'} //= HEALTH;
$context->{'vm-serial'} //= SERIAL;
$context->{'vm-cid'} //= CID;
$context->{'vm-vport'} //= VPORT;
$context->{'vm-vagrant'} //= VAGRANT;
}
sub main() {
@ -232,6 +263,59 @@ sub main() {
configure_context($context, $config);
# instructed to revert machine
if ($context->{revert}) {
if (exists $config->{aliases}->{$context->{'vm-domain'}}) {
$context->{'vm-domain'} = $config->{aliases}->{$context->{'vm-domain'}};
}
print STDERR "REVERT $context->{'vm-domain'}\n";
if ($context->{'vm-vagrant'}) {
system("virsh -c qemu:///system snapshot-revert $context->{'vm-domain'} 1");
} else {
system("virsh snapshot-revert $context->{'vm-domain'} 1");
}
exit 0;
}
# instructed to check health
if ($context->{health}) {
my $health = IO::Socket::INET->new(
PeerAddr => $context->{'vm-addr'},
PeerPort => $context->{'vm-health'},
Proto => 'tcp',
Type => SOCK_STREAM
);
if (not defined $health) {
print STDERR "Unable to connect to health $context->{'vm-addr'} $context->{'vm-health'}\n";
exit 2;
}
print $health "\n";
eval {
alarm 2;
local $SIG{ALRM} = sub { die "Health timed-out\n"; };
while (my $output = <$health>) {
last if $output eq ":END\r\n";
print $output;
}
close $health;
};
if ($@) {
print STDERR "Failed to get health: $@\n";
exit 1;
}
exit 0;
}
# load language before checking usage in order to handle -lang=? flag
# to list languages instead of showing a usage message
my $lang = load_language($context);

View File

@ -6,7 +6,7 @@
# listens for incoming commands from vm-client. Invokes vm-exec to send
# commands to the PBot Guest Server (guest-server).
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2021-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use 5.020;
@ -25,19 +25,24 @@ use Encode;
use constant {
SERVER_PORT => $ENV{PBOTVM_PORT} // 9000,
HEARTBEAT_PORT => $ENV{PBOTVM_HEART} // 5556,
DOMAIN_NAME => $ENV{PBOTVM_DOMAIN} // 'pbot-vm',
COMPILE_TIMEOUT => $ENV{PBOTVM_TIMEOUT} // 10,
};
sub vm_revert() {
sub vm_revert($input) {
return if $ENV{PBOTVM_NOREVERT};
print "Reverting vm...\n";
system('time virsh snapshot-revert '.DOMAIN_NAME.' 1');
execute("perl vm-exec -revert $input", 1000);
print "Reverted.\n";
}
sub execute($command) {
sub vm_check_health($input) {
print "Checking health...\n";
my ($ret, $result) = execute("perl vm-exec -health $input", 2);
print "$result\n" if length $result;
return ($ret, $result);
}
sub execute($command, $timeout = COMPILE_TIMEOUT) {
print "execute ($command)\n";
# to get $? from pipe
@ -53,7 +58,7 @@ sub execute($command) {
my $result = eval {
my $output = '';
local $SIG{ALRM} = sub { kill 9, $pid; die "Timed-out: $output\n"; };
alarm(COMPILE_TIMEOUT);
alarm($timeout);
while (my $line = decode('UTF-8', <$fh>)) {
$output .= $line;
@ -80,47 +85,6 @@ sub execute($command) {
return ($ret, $result);
}
sub connect_to_heartbeat() {
my $heartbeat;
my $attempts = 15;
while (!$heartbeat && $attempts > 0) {
print "Connecting to heartbeat on port ".HEARTBEAT_PORT." ... ";
$heartbeat = IO::Socket::INET->new (
PeerAddr => '127.0.0.1',
PeerPort => HEARTBEAT_PORT,
Proto => 'tcp',
Type => SOCK_STREAM,
);
if (!$heartbeat) {
print "failed.\n";
--$attempts;
print "Trying again in 2 seconds ($attempts attempts remaining) ...\n" if $attempts > 0;
sleep 2;
} else {
print "success!\n";
}
}
return $heartbeat;
}
sub do_heartbeat() {
tie my $heartbeat, 'IPC::Shareable', { key => 'heartbeat' };
tie my $running, 'IPC::Shareable', { key => 'running' };
while ($running) {
my $heartbeat_monitor = connect_to_heartbeat();
while ($running and <$heartbeat_monitor>) {
$heartbeat = time;
}
}
exit;
}
sub server_listen($port) {
my $server = IO::Socket::INET->new (
Proto => 'tcp',
@ -135,29 +99,25 @@ sub server_listen($port) {
}
sub do_server() {
tie my $heartbeat, 'IPC::Shareable', { key => 'heartbeat' };
tie my $running, 'IPC::Shareable', { key => 'running' };
print "Starting PBot VM Server on port " . SERVER_PORT . "\n";
my $server = eval { server_listen(SERVER_PORT) };
if ($@) {
print STDERR $@;
$running = 0;
return;
}
while ($running and my $client = $server->accept) {
while (my $client = $server->accept) {
print '-' x 20, "\n";
my $hostinfo = gethostbyaddr($client->peeraddr);
print "Connect from ", $client->peerhost, " at ", scalar localtime, "\n";
handle_client($client, $heartbeat);
handle_client($client);
}
print "Shutting down server.\n";
}
sub handle_client($client, $heartbeat) {
sub handle_client($client) {
my ($timed_out, $killed) = (0, 0);
my $r = fork;
@ -177,30 +137,50 @@ sub handle_client($client, $heartbeat) {
$client->autoflush(1);
eval {
my $input = eval {
# give client 5 seconds to send a line
local $SIG{ALRM} = sub { die "Client I/O timed-out\n"; };
alarm 5;
while (my $line = decode('UTF-8', <$client>)) {
$line =~ s/[\r\n]+$//;
next if $line =~ m/^\s*$/;
my $input;
while ($input = decode('UTF-8', <$client>)) {
$input =~ s/[\r\n]+$//;
next if $input =~ m/^\s*$/;
# give client 5 more seconds
alarm 5;
print "[$$] Read [$line]\n";
print "[$$] Read [$input]\n";
if (time - $heartbeat > 5) {
print "[$$] Lost heartbeat, ignoring compile attempt.\n";
print $client "Virtual machine is resetting, try again soon.\n";
# check health
my ($health, $health_message) = vm_check_health($input);
if ($health == 2) {
print "[$$] Unable to connect to VM health check, ignoring compile attempt.\n";
print $client "Virtual machine is offline.\n";
last;
}
if ($health == 1 || $health == -13) {
print "[$$] VM not responding to health check, ignoring compile attempt.\n";
print $client "Virtual machine is temporarily unavailable, try again soon.\n";
last;
}
if ($health != 0) {
if (length $health_message) {
print $client $health_message;
} else {
print $client "Virtual machine is misbehaving, try again soon.\n";
}
last;
}
# disable client time-out
alarm 0;
my ($ret, $result) = execute("perl vm-exec $line");
my ($ret, $result) = execute("perl vm-exec $input");
$result =~ s/\s+$//;
print "Ret: $ret; result: [$result]\n";
@ -217,6 +197,8 @@ sub handle_client($client, $heartbeat) {
print $client encode('UTF-8', $result . "\n");
last;
}
return $input;
};
# print client time-out exception
@ -228,11 +210,11 @@ sub handle_client($client, $heartbeat) {
print "[$$] timed out: $timed_out; killed: $killed\n";
if ($timed_out || $killed) {
vm_revert();
vm_revert($input);
}
# child done
print "[$$] client exiting\n";
print "[$$] client exit\n";
print "=" x 20, "\n";
exit;
}
@ -244,23 +226,8 @@ sub main() {
# let OS clean-up child exits
$SIG{CHLD} = 'IGNORE';
tie my $heartbeat, 'IPC::Shareable', { key => 'heartbeat', create => 1, destroy => 1 };
tie my $running, 'IPC::Shareable', { key => 'running', create => 1, destroy => 1 };
$running = 1;
$heartbeat = 0;
my $heartbeat_pid = fork // die "Heartbeat fork failed: $!";
if ($heartbeat_pid == 0) {
do_heartbeat();
} else {
do_server();
}
print "Waiting for heart to stop...\n";
waitpid($heartbeat_pid, 0);
print "Heart stopped.\n";
# start server
do_server();
}
main();

View File

@ -1,21 +1,25 @@
{
"machines" : {
"pbot-vm" : {
"addr" : "127.0.0.1",
"serial" : 5555,
"heart" : 5556,
"health" : 5556,
"cid" : 7,
"vport" : 5555
"vport" : 5555,
"vagrant" : 0
},
"pbot-test-vm" : {
"addr" : "127.0.0.1",
"serial" : 7777,
"heart" : 7778,
"health" : 7778,
"cid" : 42,
"vport" : 5555
"vport" : 5555,
"vagrant" : 1
}
},
"aliases" : {
"openSUSE" : "pbot-vm",
"main" : "pbot-vm",
"test" : "pbot-test-vm"
},
"default-machine" : "openSUSE"
"default-machine" : "main"
}

View File

@ -2,7 +2,7 @@
DOMAIN="${PBOTVM_DOMAIN:-pbot-vm}"
SERIAL="${PBOTVM_SERIAL:-5555}"
HEART="${PBOTVM_HEART:-5556}"
HEALTH="${PBOTVM_HEALTH:-5556}"
cat > serial-2.xml <<EOF
<serial type='tcp'>
@ -14,7 +14,7 @@ EOF
cat > serial-3.xml <<EOF
<serial type='tcp'>
<source mode='bind' host='127.0.0.1' service='$HEART' tls='no'/>
<source mode='bind' host='127.0.0.1' service='$HEALTH' tls='no'/>
<protocol type='raw'/>
<target port='3'/>
</serial>

View File

@ -1,6 +1,6 @@
#!/usr/bin/env perl
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2021-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use warnings;
@ -17,11 +17,11 @@ sub initialize {
$self->{default_options} = '';
$self->{cmdline} = 'bash $options $sourcefile';
$self->{cmdline_opening_comment} = ": <<'____CMDLINE____'\n";
$self->{cmdline_closing_comment} = "____CMDLINE____\n";
$self->{cmdline_opening_comment} = ": <<'CMDLINE'\n";
$self->{cmdline_closing_comment} = "CMDLINE\n";
$self->{output_opening_comment} = ": << '____OUTPUT____'\n";
$self->{output_closing_comment} = "____OUTPUT____\n";
$self->{output_opening_comment} = ": << 'OUTPUT'\n";
$self->{output_closing_comment} = "OUTPUT\n";
}
1;

View File

@ -1,6 +1,6 @@
#!/usr/bin/env perl
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2021-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use warnings;
@ -17,11 +17,11 @@ sub initialize {
$self->{default_options} = '';
$self->{cmdline} = 'ksh $options $sourcefile';
$self->{cmdline_opening_comment} = ": <<'____CMDLINE____'\n";
$self->{cmdline_closing_comment} = "____CMDLINE____\n";
$self->{cmdline_opening_comment} = ": <<'CMDLINE'\n";
$self->{cmdline_closing_comment} = "CMDLINE\n";
$self->{output_opening_comment} = ": << '____OUTPUT____'\n";
$self->{output_closing_comment} = "____OUTPUT____\n";
$self->{output_opening_comment} = ": << 'OUTPUT'\n";
$self->{output_closing_comment} = "OUTPUT\n";
}
1;

View File

@ -1,6 +1,6 @@
#!/usr/bin/env perl
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2021-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use warnings;
@ -17,11 +17,11 @@ sub initialize {
$self->{default_options} = '';
$self->{cmdline} = 'sh $options $sourcefile';
$self->{cmdline_opening_comment} = ": <<'____CMDLINE____'\n";
$self->{cmdline_closing_comment} = "____CMDLINE____\n";
$self->{cmdline_opening_comment} = ": <<'CMDLINE'\n";
$self->{cmdline_closing_comment} = "CMDLINE\n";
$self->{output_opening_comment} = ": << '____OUTPUT____'\n";
$self->{output_closing_comment} = "____OUTPUT____\n";
$self->{output_opening_comment} = ": << 'OUTPUT'\n";
$self->{output_closing_comment} = "OUTPUT\n";
}
1;

View File

@ -1,6 +1,6 @@
#!/usr/bin/env perl
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-FileCopyrightText: 2021-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
use warnings;
@ -17,11 +17,11 @@ sub initialize {
$self->{default_options} = '';
$self->{cmdline} = 'zsh $options $sourcefile';
$self->{cmdline_opening_comment} = ": <<'____CMDLINE____'\n";
$self->{cmdline_closing_comment} = "____CMDLINE____\n";
$self->{cmdline_opening_comment} = ": <<'CMDLINE'\n";
$self->{cmdline_closing_comment} = "CMDLINE\n";
$self->{output_opening_comment} = ": << '____OUTPUT____'\n";
$self->{output_closing_comment} = "____OUTPUT____\n";
$self->{output_opening_comment} = ": << 'OUTPUT'\n";
$self->{output_closing_comment} = "OUTPUT\n";
}
1;

View File

@ -4,8 +4,8 @@
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
PBOTVM_SERIAL = ENV['PBOTVM_SERIAL'] || 5555
PBOTVM_HEART = ENV['PBOTVM_HEART'] || 5556
PBOTVM_NAME = ENV['PBOTVM_NAME'] || 'pbot-vagrant-vm'
PBOTVM_HEALTH = ENV['PBOTVM_HEALTH'] || 5556
PBOTVM_DOMAIN = ENV['PBOTVM_DOMAIN'] || 'pbot-vm'
Vagrant.configure("2") do |config|
# Every Vagrant development environment requires a box. You can search for

View File

@ -9,47 +9,73 @@ section, then return to this guide.
To install vagrant on openSUSE, use:
zypper install --no-recommends vagrant vagrant-libvirt
zypper install --no-recommends vagrant
Otherwise see https://vagrant-libvirt.github.io/vagrant-libvirt/installation.html for installation instructions for your platform.
### Install vagrant-libvirt
If your distribution does not have a `vagrant-libvirt` package or if you need an up-to-date version use Vagrant's plugin manager:
vagrant plugin install vagrant-libvirt
### Start Vagrant Box
To start a virtual machine, `cd` into one of the PBot-VM Vagrant sub-directories and run the following command. This will download
the appropriate virtual machine image and automatically configure it as a PBot VM Guest.
the appropriate virtual machine image and automatically configure it as the default PBot VM Guest, `pbot-vm` described by
[`host/config/vm-exec.json`](../host/config/vm-exec.json):
vagrant up
You may pass optional environment variables to override pbot-vm default configuration (see [PBot VM Environment Variables](../../../doc/VirtualMachine.md#environment-variables)):
You may pass optional environment variables to override pbot-vm default configuration (see [PBot VM Environment Variables](../../../doc/VirtualMachine.md#environment-variables)).
For example, to create `pbot-test-vm` described by [`host/config/vm-exec.json`](../host/config/vm-exec.json):
PBOTVM_SERIAL=7777 PBOTVM_HEART=7778 vagrant up
PBOTVM_DOMAIN=pbot-test-vm PBOTVM_SERIAL=7777 PBOTVM_HEALTH=7778 vagrant up
### Connect to Vagrant Box
Use SSH to connect to the PBot VM Guest:
vagrant ssh
If you specified a `PBOTVM_DOMAIN`, e.g. `pbot-test-vm`, you must specify it:
PBOTVM_DOMAIN=pbot-test-vm vagrant ssh
### Start PBot VM Guest Server
sudo guest-server
Once connected to the PBot VM Guest via SSH, start `guest-server` in the background:
sudo nohup guest-server &> log &
Some distributions may require you to specify the full path:
sudo /usr/local/bin/guest-server
sudo nohup /usr/local/bin/guest-server &> log &
### Disconnect from Vagrant Box
Now you can type `logout` to exit the PBot VM Guest.
### Create snapshot of PBot VM Guest
After you've logged out of the PBot VM Guest with `guest-server` running in the background, create a snapshot. This allows PBot to revert to a known good state when a command times out.
If a `PBOTVM_DOMAIN` was defined, replace `pbot-vm` with that name.
virsh -c qemu:///system snapshot-create-as pbot-vm 1
### Edit vm-exec.json
If you used `vagrant up` without specifying a `PBOTVM_DOMAIN`, you must edit the [`../host/config/vm-exec.json`](../host/config/vm-exec.json)
configuration file to set the `vagrant` value to `1` for the `pbot-vm` machine.
If you have specified a `PBOTVM_DOMAIN`, ensure the appropriate entries exist in the `vm-exec.json` configuration file.
By default, `pbot-test-vm` already has `vagrant` set to `1`.
### Start PBot VM Host Server
After starting the guest-server, you must now start the host server.
../host/bin/vm-server
cd ../host/bin/
./vm-server
### Test PBot VM
In your instance of PBot, the `sh` and `cc`, etc, commands should now produce output:
<pragma-> sh echo Hello world!

View File

@ -4,15 +4,18 @@
Vagrant.configure("2") do |config|
config.vm.box_check_update = false
config.vm.hostname = PBOTVM_NAME
config.vm.define PBOTVM_DOMAIN
config.vm.hostname = PBOTVM_DOMAIN
config.vm.provider :libvirt do |libvirt|
libvirt.title = PBOTVM_DOMAIN
libvirt.default_prefix = ""
libvirt.qemuargs :value => "-chardev"
libvirt.qemuargs :value => "socket,id=charserial1,host=127.0.0.1,port=#{PBOTVM_SERIAL},server=on,wait=off"
libvirt.qemuargs :value => "-device"
libvirt.qemuargs :value => '{"driver":"isa-serial","chardev":"charserial1","id":"serial1","index":2}'
libvirt.qemuargs :value => "-chardev"
libvirt.qemuargs :value => "socket,id=charserial2,host=127.0.0.1,port=#{PBOTVM_HEART},server=on,wait=off"
libvirt.qemuargs :value => "socket,id=charserial2,host=127.0.0.1,port=#{PBOTVM_HEALTH},server=on,wait=off"
libvirt.qemuargs :value => "-device"
libvirt.qemuargs :value => '{"driver":"isa-serial","chardev":"charserial2","id":"serial2","index":3}'
end

View File

@ -4,8 +4,8 @@
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
PBOTVM_SERIAL = ENV['PBOTVM_SERIAL'] || 5555
PBOTVM_HEART = ENV['PBOTVM_HEART'] || 5556
PBOTVM_NAME = ENV['PBOTVM_NAME'] || 'pbot-vagrant-vm'
PBOTVM_HEALTH = ENV['PBOTVM_HEALTH'] || 5556
PBOTVM_DOMAIN = ENV['PBOTVM_DOMAIN'] || 'pbot-vm'
Vagrant.configure("2") do |config|
# Every Vagrant development environment requires a box. You can search for

View File

@ -353,7 +353,8 @@
* [Initial virtual machine set-up complete](VirtualMachine.md#initial-virtual-machine-set-up-complete)
* [Start PBot VM Host](VirtualMachine.md#start-pbot-vm-host)
* [Test PBot](VirtualMachine.md#test-pbot)
* [QEMU command from libvirt](VirtualMachine.md#qemu-command-from-libvirt)
* [Adding additional VMs](VirtualMachine.md#adding-additional-vms)
* [QEMU command from libvirt](VirtualMachine.md#qemu-command-from-libvirt)
<!-- md-toc-end -->
<!-- md-toc-begin -->
* [Frequently Asked Questions](FAQ.md#frequently-asked-questions)

File diff suppressed because one or more lines are too long

View File

@ -25,8 +25,8 @@ use PBot::Imports;
# These are set by the /misc/update_version script
use constant {
BUILD_NAME => "PBot",
BUILD_REVISION => 4740,
BUILD_DATE => "2024-04-07",
BUILD_REVISION => 4741,
BUILD_DATE => "2024-04-10",
};
sub initialize {}