orgvm - virtual infrastructure orchestration workbook
Table of Contents
"Restate the problem in terms of the solution." -variously attributed
0.1. About OrgVM
This is an orgvm workbook. The sections provide summary, status, and technical detail in (increasingly, gradually) more "technical" terms. You can replace this section with a description of your solution or remove (or move) to plug OrgVM.
1. Capabilities
2. INPROGRESS State
2.1. TODO Feature Checklist
[ ]
club together some working programs to pipline data[ ]
define file system users and groups[ ]
create file system structure(s)[ ]
write systemd scripts[ ]
setup source rendering (tangle/export)[ ]
setup build and integration (Makefile,…)[-]
publish (live demo at gw10.bru.st)
2.2. TODO Capability Checklist
A "functional cross-checklist" of working things to duplicate and migrate or integrate.
[ ]
comic.chat - web view of public IRC (or whatever) as comix[ ]
websocket server[ ]
IRC to comic.chat relay+[ ]
comic.chat to web relay[ ]
discord to comic.chat relay
[-]
crude-ci: drive commits on remote machines via messages from Savannah[X]
UDP to JSONL writer[ ]
JSONL to JSON CGI API
[ ]
gliphy IRC relay[ ]
samplicator[ ]
install samplicator[ ]
start-samplicator.sh
3. TODO Licensing and Documentation
Render some top-level documentation right here…
4. INPROGRESS Programs
4.1. start-samplicator
#!/bin/sh SAMPLICATE=/home/corwin/build/samplicator-1.3.8rc1/samplicate FROM_PORT=17971 TO_LIST="127.0.0.1/19790 127.0.0.1/19769" LOG_FILE=/petroglyph/samlicator.log nohup $SAMPLICATE -p $FROM_PORT \ $(echo `for h in $TO_LIST ; do echo $h ; done`) \ >$LOG_FILE 2>&1 &
4.2. udp-line-receiver
4.2.1. Documentation
- Design
Write commit meesages sent via UDP from git post-receive hooks to JSONL log files per "project", as defined by each message. Messages contain five items, space separated: PROJECT CONTEXT REVISION AUTHOR MESSAGE PROJECT: the repository name CONTEXT: the branch/tag REVISION: the new commit hash (given as $newrev) AUTHOR: the local name of the author (not the commiter) MESSAGE: the first line of the commit message
- Usage
udp-line-receiver [options] [FOLDER] Most common options: -man full documentation -basedir root for auto-selected log folder -any-project accept logs for any project -project <name> accept logs for project <name> -port <number> set the port to listen on
4.2.2. Program Headers
The "Sh-bang" line helps the operation system launch the right interpreter. Additional boilerplate (or "standard text") identifies the author and designates the program as free software by specifying a free software license (the GNU Public License).
#!/usr/bin/env perl # #
Load required packages.
use strict; use warnings qw[all]; use feature qw[say]; use IO::Async::Loop; use IO::Async::Socket; use JSON::MaybeXS; use LWP; use URI::Escape; use File::Basename qw[basename dirname]; use Getopt::Long; use Pod::Usage;
Do not buffer output to STDOUT or STDERR.
#avoid buffering output; STDOUT->autoflush; STDERR->autoflush;
4.2.3. Start-up processing
Setup defaults and handle environment variables and command-line arguments.
## vars # the port to listen on my $port = $ENV{PORT} || 17970; # access controls # accept any name or IP that exists, when empty accept any my %accept_hosts = (); # reject from any name or IP that exists my %reject_hosts = (); # keys are values to accept for project, when empty accept any my @projects = qw[emacs erc dungeon test-proj cc-test-proj]; my ( $PROGRAM ) = basename $0, qw[.pl]; my $VERSION = '0.0.1'; my $DEBUG = 1; # any truthy value for more output, sent to STDOUT my ( $default_output_path ) = ( dirname $0 ) . q(/log); my $man = 0; my $help = 0; GetOptions( 'help|?' => \$help, man => \$man, 'program=s' => \$PROGRAM, 'version=s' => \$VERSION, 'debug!' => \$DEBUG, 'port=i' => \$port, 'accept=s' => \%accept_hosts, 'reject=s' => \%reject_hosts, 'project=s' => \@projects, 'any-project' => sub { @projects = () }, 'basedir=s' => \$default_output_path, ) or pod2usage(2); pod2usage(1) if $help; pod2usage(-exitval => 0, -verbose => 2) if $man; my ( $output_path ) = @ARGV ? (@ARGV) : $default_output_path; # output path must exist unless (-e $output_path && -r _) { die qq(Missing or unreadable output path "$output_path"\n). qq(Usage: $0 [ /path/to/jsonl ]\n); } # cache existance of reject and accept lists my %projects = map { $_ => 1 } map { split /[,;\s]/ } @projects; my $accept_any_project = %projects < 1; my $no_accept_list = %accept_hosts < 1; my $no_acls_exist = $no_accept_list && %reject_hosts < 1;
4.2.4. Main loop
## start my $loop = IO::Async::Loop->new; my $socket = IO::Async::Socket->new( on_recv => sub { my ( $self, $dgram, $addr ) = @_; # ZZZ: fail2ban? # parse the datagram chomp($dgram); $DEBUG and warn 'RECV:', $dgram, "\n"; # source ADDR check unless ($no_acls_exist or not exists $reject_hosts{$addr} && ($no_acls_exist or exists $accept_hosts{$addr})) { $DEBUG and warn 'RJCT:',$addr,qq[\Q$dgram\E],"\n"; return; } my ( $project, $context, $revision, $author, $logline ) = split /[\s]+/ms, $dgram, 5; # content validations unless ($project and $context and $revision and $author and $logline and $revision =~ /^\b[0-9a-f]{5,40}$/ and ($accept_any_project or exists $projects{$project})) { $DEBUG and warn 'RJCT:',$addr,qq[\Q$dgram\E],"\n"; return; } my $jsonl_file = sprintf( '%s/%s.jsonl', $output_path, $project ); open my $fh, '>>', $jsonl_file or do { warn qq[ERR opening [[$jsonl_file][$@ $?]]:$dgram\n]; return; }; # touch-ups # rtrim commit message $logline =~ s/[\s.]+$//m; # ltrim all leading semi-colon, star, and space like chars $logline =~ s/^[\s;*]+//s; my $line = encode_json({ project => $project, context => $context, revision => $revision, author => $author, message => $logline, }) . "\n"; $DEBUG and warn qq(JSON:$line); $fh->print( $line ) or warn qq[ERR writing [[$jsonl_file][$@ $?]]:$dgram\n] } ); $loop->add( $socket ); print "Starting.."; $socket->bind( service => $port, socktype => 'dgram' ) ->then(sub{ say "listening on $port" })->get; $loop->run;
4.2.5. Manual
__DATA__ =head1 NAME udp-line-receiver - receive via UDP and write a jsonl per project =head1 SYNOPSIS =head1 ALL OPTIONS =over 8 =item B<-any-project> Enable logging for any project. =item B<-basedir> When no FOLDER is given, sets the base directory for a folder of JSON logs containing one file per project for which at least one message has been accepted. =item B<-help> Print a brief help message and exits. =item B<-man> Prints the manual page and exits. =item B<-no-debug> Disble debugging messages =item B<-port> Defines the UDP port to listen on, by default 17970. =item B<-project NAME> Enable logging for project NAME. =item B<-program NAME> Set the program name to NAME. =item B<-version VERSION> Set the project version to VERSION =back =head1 DESCRIPTION Read from a UDP port and write accepted message into a JSONL file based on the value of the first field of the message. =head1 LICENSE =cut
4.3. tailjsonl
4.3.1. Documentation
4.3.2. Start-up Processing
First there are some repetitive bits that we can skip looking at for now; see Boilerplate or the generated file for detail.
- Include Libraries
use strict; use warnings qw[all]; use feature qw[say]; use CGI; #use CGI::Carp qw[fatalsToBrowser]; use File::Basename qw[basename]; use Getopt::Long; use Pod::Usage;
- Declare Variables
my ($PROGRAM) = basename $0, qw[.pl .cgi]; my $VERSION = '0.0.50'; my $data_path = $ENV{PWD}; # both of these patterns are anchored at both ends # neither one allows single quote or backslash my $value_re = qr(^([a-zA-Z0-9_.-]+)$); my $branch_re = qr(^([/a-zA-Z0-9_.-]+)$); my ($help, $man); my $DEBUG = 1; my ( @projects, %projects ) = qw[emacs dungeon testproj test-proj cc-testproj];
- Handle Command-line
Process command line options.
sub no_dot_or_backslash (\$) { my $sref = shift; return sub { my ($field,$value) = @_; die qq("$field cannot contain dot or backslash") if $value =~ m<[.\\]>; $$sref = \$value; } } GetOptions( 'help|?' => \$help, man => \$man, 'program=s' => \$PROGRAM, 'version=s' => \$VERSION, 'debug!' => \$DEBUG, 'project=s' => \@projects, 'any-project' => sub { @projects = () }, 'basedir=s' => \$data_path, value_re => no_dot_or_backslash $value_re, branch_re => no_dot_or_backslash $branch_re, ) or pod2usage(2); pod2usage(1) if $help; pod2usage(-exitval => 0, -verbose => 2) if $man;
- Sanity Checks
Sanity checks before starting
# create a hash look-up table for given valid projects # don't check existance in case of creation after we start %projects = map { $_ => 1 } grep /$value_re/, map { split /[,:;\s]+/ } @projects; # optimize: store the fact of any empty project list my $allow_any_project = 0 == %projects; # an empty project list means a wide open query # if we have values in @projects then --any-project # was not used or --project was also used; ask for clarity. unless( %projects or $allow_any_project and not @projects ) { die qq(STOP possible uninteded unrestricted query enabled\n) . qq(use --any-project and avoid --project to clarify.); }
4.3.3. Main Program
sub fail_with ($$) { my ( $q, $status ) = @_; print $q->header( -status => $status ); exit; } my $q = new CGI; my ($project) = ($q->param('project') || $q->param('p') || '' ) =~ $value_re; my $data_file = $data_path . '/' . $project . '.jsonl'; fail_with $q, q(401 Bad project) unless $project and ($allow_any_project || exists $projects{$project}) and -r $data_file; my ($filter) = ($q->param('branch') || $q->param('b') || '' ) =~ $branch_re; my $command = $filter ? qq(grep -F '"context":"$filter"' $data_file | tail -1) : qq(tail -1 $data_file); my ($prop) = ($q->param('property') || $q->param('prop') || $q->param('P') || '' ) =~ $value_re; $command .= qq( | jq -r '.$prop') if $prop; my $result = `($command) 2>/dev/null`; print $q->header( -type => q(application/json) ), $result || 'null'
5. INPROGRESS Development
Programs to run within Emacs.
5.1. HTML rendering
To render HTML from this document place your cursor (point
, in Emacs terms) into the code below and press C-c C-c
(control+c
, twice).
(org-html-export-to-html)
5.2. Project source rendering
To render project sources from this document place your cursor (point
, in Emacs terms) into the code below and press C-c C-c
(control+c
, twice).
(org-babel-tangle)
6. Integration (in shell)
These programs run from the command line and shell-scripts.
6.1. build.sh
#!/usr/bin/sh emacs --batch -l build.el
6.2. tangle-README.sh
#!/usr/bin/sh emacs --batch -eval '(progn (setopt org-safe-remote-resources '"'"'("\\`https://fniessen\\.github\\.io/org-html-themes/org/theme-readtheorg\\.setup\\'"'"'" "\\`https://fniessen\\.github\\.io/org-html-themes/org/theme-bigblow\\.setup\\'"'"'")) (find-file "README.org") (org-babel-tangle))'
6.3. build.el
;;; build.el --- build README.md from README.org -*- lexical-binding: t; -*- ;; Author: Corwin Brust <corwin@bru.st> ;; ;;; Commentary: ;; tangle sources then build README.{md,html,txt} from README.org ;;; Code: (require 'ox-md) ;; maybe something to install ox-md from MELPA? (require 'htmlize "/var/c2e2/orgvm/site-lisp/htmlize/htmlize.el" t) (setopt org-safe-remote-resources '("\\`https://fniessen\\.github\\.io/org-html-themes/org/theme-readtheorg\\.setup\\'" "\\`https://fniessen\\.github\\.io/org-html-themes/org/theme-bigblow\\.setup\\'")) (org-babel-do-load-languages 'org-babel-load-languages '((C . t) (ditaa . t) (dot . t) ;; (http . t) ; local source (emacs-lisp . t) (latex . t) ;(ruby . t) (js . t) (perl . t) (plantuml . t) (shell . t) ;; (typescript . t) ; not always loaded )) (save-excursion (find-file "README.org") (org-babel-tangle) (org-md-export-to-markdown) (org-html-export-to-html) ;;(org-latex-export-to-pdf) ;; ugly, omit for now (org-ascii-export-to-ascii)) (provide 'build) ;;; build.el ends here
7. Boilerplate
The "Sh-bang" line helps the operation system launch the right interpreter.
#!/usr/bin/env perl #
This additional "boilerplate" (or "standard text") which identifies the author and designates the program as free software by specifying a free software license (the GNU Public License).
Copyright 2025 Corwin Brust <corwin@bru.st> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.