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.

0.1.1. Executive Summary

  • invoke Emacs in batch to
    • [X] execute lambda via code blocks (babel)
    • [-] render (tangle, orchestrate, iterate)
      • [X] given documents in given formats (export; MD, PDF, txt, HTML)
      • [X] source files (tangle; whatever org-babel supports)
      • [X] manual pages (export; perl,POD to troff via Pod::Usage)
      • [-] website/UI (HTML via export)
      • [ ] render dpkg
    • [ ] query (GET/PUT) data in org documents
      • [X] shell API
      • [ ] CGI API
  • [ ] individual user accounts (unique sub-domain + hosting for org documents and sundry)
    • [ ] user account directly on an orgvm host
    • [ ] user account within containerized environment
    • [ ] run emacsclient as user (per user emacs –server)
    • [ ] user registration and preferences and whatever UIs for that

0.1.2. Even More About OrgVM

In general the level of complexity increases from the from start of the workbook (which typically include lists of things the sum set of solutions described can do, and "instruments" and "controls" to monitor, to the end of the work-book (which usually starts with documentation following by implementation in increasing, building complexity). Among all sections (and considered all included and referenced materials), an orgvm workbook should provide a technical means to access (this, potentially, inspect, etc.) all of the detailed implementation instructions and other materials (interim files, "work product", system conditions, configurations, commands to run, log-files, manual pages and/or their sources, and so on) within the intended scope (so, such as to create, install, and operate ) the net solution; however, such materials (meaning as said before and now particularly including such as repositories or files, fragments, documentation, media, and other "assets" as may be external to -thus referenced within- this workbook).

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

  1. 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
    
  2. 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

  1. Design
    return the notification detail for latest revision of given project
    
  2. Usage

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.

  1. 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;
    
  2. 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];
    
  3. 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;
    
  4. 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/.

Author: Corwin Brust

Created: 2025-08-10 Sun 21:24

Validate