     * Program Description
          + User-Configurable Variables
          + Compilation Tool Setup
          + The CMakeList
          + Compile and Runtime Bugs
          + Operation
          + Known Issues
     * Source Code
Program Description

   http://greatfractal.com/LouiaObScura.html

   spartan://greatfractal.com/louiaobscura.gmi

   (This page is an extremely rough draft and is full of all kinds of
   errors. I will try to improve the documentation over time if I release
   future versions. Please note that this page was generated in HTML on
   12/5/2025
   . If you are reading this as a README file inside the source tarball or
   served via .txt file, it may have odd formatting and will not contain
   any example hyperlinks.

   Also, please don't use this software--it is experimental,
   proof-of-concept only and is full of bugs and inconsistent behavior. I
   have created this software for my own use only, providing information
   about the project in the hopes it will inspire others to learn more
   about Lua and MicroLua, Spartan and Smolnet, and Coroutines. It's an
   obscure project that might not be suitable for you, and I am not
   responsible for the correctness of the information and do not warrant
   it in any way.)

   Louia ObScura is first and foremost a Lua 5.5 Spartan protocol (a type
   of small-Internet or "Smolnet" protocol) TCP-based server designed to
   run on the Raspberry Pi Pico 2 W with RP2350 MCU, but it also works on
   the original Pico W with RP2040, albeit with half the RAM and flash
   storage. It is a port of the Lua 5.1 and Linux-based Louia that I
   first released in February 2025 and programmed in St. Louis, Missouri,
   hence the name Lua + Louis = Louia. Both Louia and Louia ObScura use
   cooperative-multitasking via Lua coroutines. However, Louia ObScura
   became an operating system in itself, hence the capital O and S in
   ObScura, as I had to implement many of the systems that were missing
   once I moved it to the Pico (with no OS) and decided to expose the
   underlying structure I already had in place. Louia ObScura most heavily
   relies on MicroLua, which, at the time of this writing in November
   2025, does not specifically support the newer Pico 2 W, so I also had
   to design and include various workarounds to get a functional server. I
   don't use the (very convenient) MicroLua threading system--I used my
   own that came from the original Louia, but am glad it had threading in
   mind with its many yielding and event-based functions.

   If you are reading this from the README file, the full project page is
   shown at the HTTP link above and a brief page is also hosted on my
   Spartan site itself (and by itself). This README only includes a few
   technical sections and doesn't go into as much detail as the project
   page, which includes a description of how the OS operates along with
   the challenges and fun I had with this project.

   The Spartan server in Louia ObScura is derived from the same Louia
   server from my original Linux project that was first released in
   February 2025, adapted to the lower resources and lack of Linux on the
   Pico boards. So some of those variables will be similar, but since it
   doesn't accept command-line parameters (there isn't any Linux anymore),
   some additional variables were added to the CMake CMakeLists.txt file
   that MicroLua uses to compile. And a large list of additional variables
   were added to account for the OS portion of the project.

   Not only did I have to replace the missing functions that Linux
   previously provided, for practical use, I also had to figure out how to
   ease administration and maintenance of the server, too. As I described
   in detail on the project page, the OS in Louia ObScura really is an OS
   by definition, and thankfully, a Spartan browser and the protocol's
   upload and redirect ability, along with USB TTY, will perform the
   user-interface function similar to a rudimentary web app. But I still
   had to implement these functions:
     * Cooperative multitasking via coroutines (this system was derived
       from Louia)
     * Pseudo FastCGI and batch processing capability
     * Text mode screen buffer (what I call PID files)
     * Lua scripting API (tiny, just 3 functions)
     * Task launching, expiration, and killing
     * Privilege separation
     * Sandbox and error isolation
     * IPC message queues and context switching
     * OOM killer and auto file deletion
     * Task scheduler/prioritization
     * Bulk site upload delete/receive function and storage formatter
     * Log viewing and deletion
     * Administration console
     * Directory and process listing
     * Custom RTC/NTP like system

   And then, of course, there are all of the features of the Louia Spartan
   server itself, which I won't go into here.

   Lua, being a small, fast programming language, lacks a TCP/IP stack and
   file-system by design, but MicroLua, along with incorporating the Pico
   C API, thankfully also included APIs into the other open-source
   projects of LittleFS and lwIP for the file system and TCP/IP stack,
   respectively, along with an API into the Wifi CYW43 driver, which
   removed the need to create those systems from scratch. However, it took
   significant design and configuration to successfully incorporate them
   into Louia ObScura.

   I first put Louia ObScura 1.0 online on the Pico 2 W in November 2025,
   9 months after I released Louia 1.0 on Linux, which allowed me to tweak
   the final design. I designed it to make it easy for me to maintain,
   like I did for my recursive, Perl-based HTML site generator in 2014
   that currently hosts this original HTTP project page on Pi Zeros. It's
   only my second Lua program to date (my 3rd if you count Louia itself
   which it just builds upon) which shows just how practical and elegant
   the Lua language is. While C and MicroPython are official languages for
   it (and I've used both C and Python 2/3 on other small-computing
   projects), they would drain the fun out of it for me in this project
   and forego Lua's unique advantages over both.

   My object-free, functional-programming-free source code may look simple
   after the fact (and it is indeed riddled with bugs and uses many
   inefficient patterns that I haven't got around to fixing), but when you
   design something like this from scratch, it is not so simple to do, as
   you have to imagine the entire layout of the future system, insert your
   design patterns, and hope it all comes together technically in the end,
   being practical and workable. Thankfully Lua, Unix/Linux, and the long
   history of programming language and OS/system design and administration
   over the years has given us a mental language and map that makes
   manipulating these ideas much easier; back when I was programming in
   BASIC and ML on the Commodore 64 to write a BBS over a 450-baud,
   overclocked, dial-up Volksmodem as a teenager, I would not have been
   able to grasp multiuser and networking abstractions like I could 4
   decades later, nor was there a common language and infrastructure to
   support them even if I could. But that C64 experience made programming
   this project so much easier as well, as you were essentially building
   pieces of an OS anytime you had to program something, just not tying
   them all together into a reusable, generalized abstraction. Its 1986
   version of GEOS was indeed a graphical OS that tied many things
   together, a formidable undertaking that ran well on the C128 under GEOS
   128 2.0 using RAM Expansion Units, but those still could not multitask
   unlike the later 16-bit PC version. Multitasking, even the
   primitive cooperative version that I used, is difficult to do on
   resource-limited systems; even the first 1984 Mac (with 128 K) could
   not do it upon release. But Lua with its coroutines is comparably
   small, and the Picos, while seemingly tiny, are comparatively large, a
   fitting combination.

User-Configurable Variables

The first set of variables is the same as the LouiaServer, except they
are stored in CMakeLists.txt instead of in the main file, with the exception
that TIMEOUTCHECKSECS had to be set as a string in order to pass fractional,
sub-second decimal values (which are converted to a floating-point number
later). The DOWNLOADBUFFER default was lowered to account for the lower
memory of the Picos. Adjusting my variables to this format created some
problems, as I could not easily pass some symbols, and it did not like ""
empty strings, so I had to pass negation strings which corrupted the program
logic in some ways.

   HOMEPATH The full path and page name of the first page if no page is
   indicated. This is relative to the domain and site directory and not
   necessarily an actual file system path. Default is "/index.gmi".
   MIME An array (table) of extensions to MIME types and optional
   charsets, for example: {gmi="text/gemini; charset=utf-8",
   html="text/html; charset=utf-8"} Leave out the period on the extension.
   In STRICTMODE, the extensions longer than 3 characters are ignored.
   Note that this parameter is not in CMakeLists.txt for Louia ObScura and
   is defined in the main source instead. Default includes .gmi, .txt,
   .jpg, .gif, .png, .pdf, .tgz, .pid, .lua, and .log.
   NOMIME Sets the MIME type for pages with no MIME entry or extension.
   Not applicable in STRICTMODE. In Louia ObScura the semicolon had to be
   escaped with 4 backslashes to get through CMakeLists.txt unscathed.
   Default is "text/gemini\\\\; charset=utf-8"
   MAXTHREADS The upper cap on the number of coroutines to spawn to limit
   excessive memory use. If a lot of memory is available and a lot of
   requests coming in at once are expected, then this can be increased
   further. Default is 100.
   MAXPATHLEN The maximum number of bytes for a directory path plus the
   filename. In Louia ObScura, this applies to LittleFS and not a Linux
   filesystem. Default is 80.
   MAXUPLOADBYTES The max number of bytes for uploads if uploads are
   turned on. It should be large enough to hold the date and hour string
   and PID value if the RTC uploads and scripting is used. Default is 32.
   MAXREQBYTES The total max number of bytes in a Spartan request line. It
   should be bigger than MAXPATHLEN since it also includes the path to the
   file. And it should be bigger than MAXUPLOADBYTES if uploads are turned
   on. If it ever hits this limit, the connection closes and the request
   is not processed. Default is 100.
   DOWNLOADBUFFER The number of bytes for a chunk of RAM per coroutine for
   big downloads. Files smaller than this size are sent all at once for
   speed. This size also affects how often it yields to other coroutines
   (smaller chunks allow better concurrency and use less RAM, but are
   slightly slower and increase CPU). In Louia ObScura that uses lwIP on
   the Pico, values significantly smaller or larger than 512 had worse
   performance. Default is 512.
   TIMEOUTSECS The number of seconds before a connection will timeout if
   the request in progress has not yet been completed. It has to be large
   enough to allow a file transfer of the largest size needed over the
   expected network speeds, yet excessively-large timeouts may not kill
   errant coroutines fast enough to release memory for other connections
   if system memory is limited. Default is 120.
   TIMEOUTCHECKSECS The number of seconds to control coroutine concurrency
   speeds, especially during large chunking downloads that exceed
   DOWNLOADBUFFER. However, it also controls the number of seconds to
   enforce the TIMEOUTSECS above so that the coroutine can be killed.
   Lower values check more often but use more CPU, and vice-versa.Besides
   integers, it also accepts sub-second fractions that are a good balance
   between speed and CPU. In Louia, when any coroutines are in progress or
   if POWERSAVEMODE is disabled, the fastest timeout value of 0 uses
   almost 100% CPU on a Raspberry Pi Zero, yet a timeout value of .01
   seconds uses around 4% and is still quite fast. A timeout value of .001
   is even faster (fast enough for the TCP slow-start algorithm to kick
   in) but uses about 24% CPU on the Pi Zero. But in Louia ObScura on the
   Picos, this has a slightly different implementation and MicroLua's
   background functions might need time for processing, but .000001 works
   well. In Louia ObScura this value also had to be defined as a string
   which is converted to floating-point later. Default is ".000001".
   DEBUG If true, this prints extra information to stdout to assist in
   troubleshooting, similar to a verbose mode. However, the output is
   often too fast to read, so slowing down the dispatch loop with a large
   value in TIMEOUTCHECKSECS, say 3 seconds instead of .01 seconds, helps
   in debugging. Default is false.
   UPLOADSENABLED If true, this enables the Spartan upload protocol that
   uses the input prompt, but only for an internal upload variable. More
   details below. In Louia ObScura, it is needed for the TIMEPATH,
   ADMINPATH, and .lua paths which rely on it if SCRIPTSENABLED is true.
   Default is true.
   SAFEFILES If true, this preloads an array on boot, and also adds any
   site uploads or optional auto-generated .pid files upon creation, that
   contains all of the full filenames and paths in the site directory and
   forces any valid requests to check this array rather than check the
   site directory for that file directly, which is safer. However, any new
   filenames added to DIR will not be accessible until either the server
   is restarted or the RELOADPATH is accessed (see below) to pick up the
   new names. Default is true.
   POWERSAVEMODE If true, this blocks all Lua program activity and
   dispatch looping for 14 minutes if no coroutines are in progress until
   the next TCP/IP connection comes in on that port, creating an idle
   state to save power when nothing is happening. It cannot stay blocked
   forever like Louia 1.4 and later since it needs to occasionally check
   to see if the Wifi connection is still up and reconnect, if needed. It
   has no effect if there are any coroutines still running until the last
   one closes or times out, and then it goes back into effect. If false,
   the program runs at full speed all the time, only slowed by
   TIMEOUTCHECKSECS. Default is true.
   RELOADPATH The full path and page name of a fake page that should be
   named something non-obvious for admin use only. On accessing this page
   from a client, a reload will take place to find all files in the site
   directory (including sub-directories) and add them to an in-memory
   array which is used to restrict what files are allowed to be accessed
   if SAFEFILES is enabled, creating a whitelist. For example, if new
   files with new filenames are added to the site directory, and SAFEFILES
   is enabled, they will not be accessible until the RELOADPATH is
   accessed at least once. Default is "/reloadme".
   STRICTMODE If true, this provides strict request filtering and only
   allows ASCII alphanumeric, underscore, hyphen in the request, forces a
   3 character file extension, does not serve any files without an
   extension listed in the MIME array, disables percent-decoding, only
   allows alphanumeric, spaces, and hyphens in uploads, and only allows
   one subdirectory level to be traversed below the main level. Default is
   true.

This next set of variables, however, is unique to Louia ObScura:

   MAINTMODE If true, only the IP address in SAFEIP can browse the site,
   and other IPs will receive the message, "The server is currently under
   maintenance. Please come back later." This allows the admin to test
   publishing in absence of a private Linux test server before going live.
   Default is false.
   NICE An array (table) of pairs of 3-character extensions to priority
   values, with the priority being from 1 to 5, the smallest value being
   the highest priority, analogous to Linux nice but with only 5 values.
   Note that this parameter is not in CMakeLists.txt for Louia ObScura and
   is defined in the main source instead. Default is {gmi=1, txt=2, jpg=3,
   tgz=4, lua=5}.
   MAXMEMBYTES The max number of bytes of used memory as reported by
   mem.mallinfo() before the OOM Killer kicks in and kills the coroutine
   that will expire the soonest (usually the oldest one). Should be set to
   a reasonable value below the max memory of the Pico W or Pico 2 W.
   Default is 300000 assuming a Pico 2 W, half that for a Pico W
   TIMEOUTUPLOADSITESECS The number of seconds before a site upload
   process on the server side will timeout if the upload has taken too
   long. Note that there is no timeout on the client side via the
   upload.sh BASH script, and that script will have to be killed with a
   CTRL-C if it locks up. But the server should become operational again
   after the timeout and then another site upload can be attempted. This
   is very unreliable though, and the server may crash and require a reset
   if it does not timeout. Default is 90 seconds.
   TIMEOUTSCRIPTSECS The number of seconds before a connection running a
   .lua script will timeout if the request in progress has not yet been
   completed. Default is 1800 for 30 minutes.
   CONFIGPASS An optional configuration password that can be set that
   prevents unauthorized access to the TTY admin config on initial boot if
   the device is connected via USB. Security is very weak and encrypted,
   but may be better than nothing in some situations. Default is "louia".
   CONFIGTIMEOUTSECS The number of seconds the option to edit the config
   via TTY is allowed after the device is first plugged into USB before it
   will proceed with the final boot-up process. It is sort of like the
   timer in a Linux GRUB boot menu. Default is 7.
   WIFI_SSID The SSID of the wifi router to connect to after boot. Default
   is "changeme".
   WIFI_PASSWORD The wifi password of the router to connect to using WPA2
   AES. Has only been tested with up to 64 bytes hexadecimal only. Unknown
   if passphrase works. Default is
   "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789".
   WIFI_BSSID The reported MAC address of the wifi device that the router
   will see. This is a series of hex bytes represented like the default
   shown. Note that each \x has to be escaped with an additional
   backslash. Depending on the hex values, this could cause a collision
   with the delimiter used. Default is "\\xAB\\xCD\\xEF\\x01\\x23\\x45".
   WIFI_COUNTRY The cwy43 country code for the wifi driver. See the cwy43
   driver source for the appropriate two-letter code. Default is "US".
   PORT The primary TCP/IP port the server listens on. Default is 300.
   UPLOADSITEPORT The TCP/IP port used for admin site uploads (not Spartan
   uploads). Default is 301.
   LOGPATH The full path of the log file, including any subdirectories,
   using Linux-like LittleFS syntax. It must include a .log extension. If
   set to "none" the logging is disabled. However it cannot be re-enabled
   once disabled and there are inconsistencies and still partial functions
   present, as it really only stops writing to the log or creating one on
   format, it doesn't remove existing log or disable the clearlog
   function. Once set it should remain that way until the server is
   formatted again to change. Note that this sits under DIR. Default is
   "/log/spartan.log".
   CONFIGPATH The full path of the config file that is created after boot
   based on the CMakeLists.txt entries. It uses a separate partition and
   has to sit at the root directory. Default is "/config".
   ADMINPATH The full path and page name of a .lua script that generates
   an administration page that should be named something non-obvious for
   admin use only. If SAFEIP is set, then only the authorized IP can be
   used to access this page, adding a little more security. Default is
   "/scripts/admin.lua".
   TESTSCRIPTPATH The full path and page name of a .lua script used to
   show proof of concept of the Operating System that runs Louia ObScura.
   There is a link that launches it on the admin console page. Default is
   "/scripts/obscura.lua".
   SAFEIP The IPv4 IP address of the admin superuser of the Louia ObScura
   server. If set, only that IP can be used to access the various admin
   paths of the server such as ADMINPATH, RELOADPATH, FORMATPATH,
   UPLOADSITEPATH, CLEARLOGPATH, DELETEPIDFILESPATH or anything with the
   .rip, .del, .pid, .log, or .lua extensions. Note that the .lua
   extension also has to be a Spartan upload in order to be run (downloads
   are prohibited). Set to "any" to allow any IP, but it blocks
   DOMAINLOCAL and not a good idea on a public network since the admin
   paths would be public and the server exposed. Default is
   "192.168.0.101".
   RTCIP The IPv4 IP address of a fixed server that is checked to see if a
   "YYYY-MM-DD HH" date/hour upload to the TIMEPATH is allowed. Set to
   "any" to allow any IP, but it blocks DOMAINLOCAL and not a good idea on
   a public network. Default is "192.168.0.102".
   MYIP The IPv4 IP address of the Louia ObScura server if a static IP is
   used instead of DHCP. Set to "dhcp" if a static IP is not used. Default
   is "dhcp".
   MYGATEWAY The IPv4 IP address of the router gateway that the Louia
   ObScura server connects to if a static IP is used instead of DHCP. Set
   to "dhcp" if a static IP is not used. Default is "dhcp".
   MYNETMASK The IPv4 IP address of the netmask that the Louia ObScura
   server uses if a static IP is used instead of DHCP. Set to "dhcp" if a
   static IP is not used. Default is "dhcp".
   SCRIPTSENABLED If true, this enables the running of Lua .lua scripts in
   both a pseudo FastCGI like fashion and also a persistent and batch-like
   fashion. Default is true.
   NICEENABLED If true, this enables the experimental and rudimentary task
   prioritization scheduler. Default is false.
   SAFESCRIPT If true, this enables the experimental and rudimentary task
   sandboxer, limiting the task to only certain functions along with 3
   custom API functions, pidinput(), pidread(), and pidprint(), preventing
   access to the _G Global Environment. If false, access to the Global
   Environment is allowed. Turning this off is problematic and often
   causes scripts to fail. Default is true.
   TIMEPATH The full path and page name of a fake page that should be
   named something non-obvious for admin use only. On uploading a
   specially-crafted real-time date string and hour value "YYYY-MM-DD HH"
   to this path using a special BASH one-liner once an hour using cron,
   the Louia ObScura server fetches a pseudo NTP value with the remaining
   60 minutes (or 3600 seconds) being handled by the Pico's absolute time
   counter, until the next upload is received. This acts as as a pseudo
   NTP/RTC without any direct NTP or RTC on the Pico being used. Useful
   for accurate log timestamps. RTCIP is checked for authorization.
   Default is "/settime".
   TIMEOFFSETUS The number of microseconds to offset the real-time date
   and hour received via an external server, to take into account the
   delays in cron and request/upload processing to improve accuracy.
   Default is 1000000 for a -1 sec offset.
   FORMATPATH The full path and page name of a fake page that should be
   named something non-obvious for admin use only. On accessing this page,
   it immediately formats the site data partition of the drive (it does
   not touch the config partion). SAFEIP is checked for authorization.
   Default is "/format"
   UPLOADSITEPATH The full path and page name of a fake page that should
   be named something non-obvious for admin use only. On accessing this
   page, it halts all active coroutines and temporarily listens on a
   second TCP/IP port UPLOADSITEPORT for a series of: filename, binary
   file data, filename, binary file data, etc..., acting as a whole-site
   uploader used by the admin to copy files to the server in the absence
   of typical Linux cp, sftp, or rync tools. It does not use the Spartan
   upload protocol like the other functions, nor use the main TCP/IP port,
   adding some security if this port is blocked by a firewall. But the
   upload protocol is just a simple echo, cat, and netcat BASH
   pipeline/one-liner that has no error checking other than what TCP has,
   and is timing-dependent, often getting out of sync and failing. If a
   file exists with the same name, the upload replaces that file with the
   new one. When done, it closes that temporary port and resumes the
   coroutines that were previously in progress if they haven't expired or
   the client timed out. SAFEIP is checked for authorization. Default is
   "/uploadsite"
   DELETEPIDFILESPATH The full path and page name of a fake page that
   should be named something non-obvious for admin use only. On accessing
   this page, it immediately deletes all .pid files. While adding a .del
   extension to a PID page will delete the individual page, this path
   deletes all PID pages. SAFEIP is checked for authorization. Default is
   "/deletepids"
   CLEARLOGPATH The full path and page name of a fake page that should be
   named something non-obvious for admin use only. On accessing this page,
   it immediately erases the log. SAFEIP is checked for authorization.
   Default is "/clearlog"
   MAXLOGBYTES The max number of bytes that the size of the .log file can
   reach before it is auto-cleared. Default is 100000 for 100K for Pico 2
   W, 50000 for 50K for Pico W.
   MAXPIDFILEBYTES The max number of bytes that the size of a .pid file
   can reach before it is auto-cleared. Default is 1000 for 1K.
   DOMAIN: The site domain name. All Spartan requests must use that name
   or the request will be denied. Default is "pico".
   DOMAINLOCAL: A domain name that should only be accessed from within a
   private local area network. If SAFEIP is assigned, then this will be
   the domain it will use just for that client to allow private access
   from behind a firewall. The DOMAINLOCAL name is usually assigned in
   /etc/hosts on a Linux client. Default is "picolocal".
   DIR The full directory path that holds the site data files in
   Linux-like LittleFS syntax, excluding the trailing slash. Every file on
   the site except for the config file (which is in its own separate
   partition) must go in this directory, but this directory is invisible
   and forms the root of the Spartan URL path. Default is "/louia".
   MKDIRLIST This is a colon-delimited series of directory paths,
   excluding the trailing slashes, that will be deserialized and used by
   the Louia ObScura server on boot to create the initial empty directory
   structure for the site. It must include the DIR path at minimum, and
   any other directories that any other paths may refer to or it will
   error out. Default is
   "/louia:/louia/images:/louia/files:/louia/scripts:/louia/log".

But this final set of variables is intended to act like a constant or ROM for
the Picos, and these are not allowed to be changed or saved to a
configuration file once booted:

   ROM_VERSION The version of the Louia ObScura server. Default is the
   actual source version, such as "1.2"
   ROM_MAXFLASHSIZE The size in bytes of the flash on the Pico board. It
   should be 4194304 for the Pico 2 W (4 MiB) or 2097152 for the Pico W (2
   MiB). If this is changed, it will require a format of the main data
   partition, erasing all site data. Default is 4194304.
   ROM_MAXDATAPARTSIZE The size in bytes of the partition that holds the
   site data at the end of the flash. It has to allow for both the program
   data and the config partition to fit, and cannot be too large or it
   will overwrite the program data on first boot and format. A value of
   3407872 works well with the Pico 2 W (3 MiB + 256 KiB) in Louia ObScura
   1.2, and 1310720 for the Pico W (1 MiB + 256 KiB) which is cutting it
   pretty close to the program data (another 256 KiB will cause it to
   malfunction) and I'm not quite sure where the limit is. If this is
   changed, it will require a format of the main data partition, erasing
   all site data. Default is 3407872 for the Pico 2 W, and 1310720 for the
   Pico W.
   ROM_MAXCONFIGPARTSIZE The size in bytes of the config partition that
   holds the config file that is created on first boot. It is created just
   before the site data partition, leaving a gap between itself and the
   program data. So it can't be too large or it will overwrite the program
   data on boot. For LittleFS, a value of 8192 works but 4096 doesn't,
   16384 was needed to reliably write all of the config values. If this is
   changed, it will require a format of the config partition, erasing any
   saved config data. Default is 16384.

Compilation Tool Setup

   On Ubuntu 24.04 LTS, first I had to install the build tools (which
   includes cross-compilation libraries since the two machines do not
   share architectures). Then I installed minicom to see the output of the
   USB:

   sudo apt install cmake git build-essential gcc-arm-none-eabi minicom

   Then in my home directory and did:

   git clone https://github.com/MicroLua/MicroLua

   This downloaded the MicroLua source and put it into the MicroLua
   directory.

   Then the submodules have to be initialized which requires changing to
   the MicroLua directory and running:

   git submodule update --init
   git -C ext/pico-sdk submodule update --init

The CMakeList

   Now, let's start with the simplest situation, 3 files:
     * CMakeLists.txt
     * main.lua
     * mlua_import.cmake

   Unless you've used CMake↗ before, the C-like CMakeLists.txt syntax
   can be confusing, since the names between the Pico SDK and the names in
   your actual Lua code are slightly different syntax. I also frequently
   found myself accidentally using Lua "--" style comments or C "#" style
   comments in the wrong files, since I had to switch between them so
   often. If you use a W for wireless networking like I did, there is also
   the MicroLua/lib/pico/include_lwip/lwipopts.h file which has several C
   define statements that set default values for the compile. They can be
   edited there, or better yet, overrided in the CMakeLists.txt file, but
   again the syntax is different between the two files.

   Even if you don't edit it, it's still good to look in the lwipopts.h to
   see what define values you may want to change and override, but it also
   does not have all of the values, and you may need to look at the lwIP
   project itself and extrapolate back.

   That's one thing you have to get used to, looking at the underlying C
   source from time to time to find defines or default values not listed
   in the documentation. Lua and C are close pals, but I was hoping to
   avoid having to look at C so often. But if you want super granular
   control on everything the Pico does, you have to use C, as that isn't
   the point of using Lua anyway.

   For Louia ObScura, those are the only 3 files I needed for compilation,
   similar to the MicroLua and I put them in a separate directory I named
   "louia" to get ready to perform the compilation, similar to the
   MicroLua core example. Only CMakeLists.txt and main.lua contain the
   customized LouiaObScura code, mlua_import.cmake just comes from
   the MicroLua project and was left as is. CMakeLists.txt is where most
   of the main variables and parameters are defined and main.lua contains
   the primary source for LouiaObScura.

Compile and Runtime Bugs

   First I had to set an environmental variable by exporting the path of
   where the MicroLua directory was in my case:
     * export MLUA_PATH="${HOME}/MicroLua"

   Then, in my CMakeLists.txt file, for the Pico 2 W, I had to ensure that
   the following was added near the top:
     * set(PICO_BOARD pico2_w)

   For the Pico W though, there is a line I added for the pico_w board to
   uncomment instead if that board it used, and then comment out the
   pico2_w one.

   There is also a section at the bottom of that file that I customized
   for Louia ObScura that has another section commented out for the Pico
   W, as I made the Pico 2 W primary, so the appropriate section needs to
   be either commented out or uncommented for the board to ensure the
   partition sizes and memory limits are suitable for that board. The Pico
   W section should work for both, being smaller, but ideally, the larger
   values should be used for the Pico 2 W to take advantage of its full
   resources.

   I also ensure that that appropriate network parameters are set to
   connect to my DHCP server with the appropriate MAC address so my static
   DHCP works correctly (although I'm unsure if my WIFI_BSSID parameter
   actually does anything).

   I had to make sure the following was installed in Ubuntu, the first of
   which is required for picotool (which is required by the Pico SDK as of
   version 2.0.0 or later) to access USB:

   sudo apt install libusb-1.0-0-dev pkg-config

   Then I had to install picotool from GitHub. Back in the home directory
   again I ran this to fetch the most current source:

   git clone https://github.com/raspberrypi/picotool

   This creates a picotool directory. Then I had to set another
   environmental variable:
     * export PICO_SDK_PATH="${HOME}/MicroLua/ext/pico-sdk"

   Then I had to change to that ~/MicroLua/ext/pico-sdk directory (not
   picotool) and initialize another submodule:

   git submodule update --init lib/mbedtls

   Then it is just a matter of creating a "build" directory inside the
   picotool directory. Then change to this build directory and run:

   cmake ..
   make

   When done, the picotool binary executable should then be present in the
   picotool/build folder. It can be tested by running it. I create an
   alias to this binary whenever I need to use the tool. But this binary
   has to be in the path for the SDK or it will not compile, so I create a
   symlink:

   sudo ln -s ~/build/picotool/build/picotool /usr/local/bin/picotool

   Then, I change to my ~/louia directory where the 3 files are and run
   these two commands in sequence to finally compile Louia ObScura:
     * cmake -B build -DPICO_BOARD=pico2_w
     * cmake --build build --parallel=9

   However, the first compile crashes out due to a bug (see below for the
   fix).

   But once I get a successful compile, the .elf file will appear in my
   louia directory, and I can then use picotool to write the .elf file to
   the Pico's flash over USB. After changing to the louia directory, I put
   the device in BOOTSEL mode using the button ritual and do:

   picotool load --update --execute build/Louia_ObScura.elf
   --force-no-reboot

   Of course I create aliases to simplify everything.

   For the original Pico W that I tried later, I just removed the "2"
   above and recompiled, which required that I first delete my build
   directory (I'm sure cmake has a way around this, maybe delete the
   CMakeCache.txt file in the build directory instead which seemed to work
   for me) so switching and recompiling between the two platforms was a
   little slower.

I could not get my first cross-compile to work under Ubuntu 24.04

   The bug that crashes out the first compile shows this kind of error,
   along with a lot of related ones like it: note: 'PRId64' is defined in
   header '<inttypes.h>'; did you forget to '#include <inttypes.h>'

   To fix it, I had to go back to my home directory where the MicroLua
   folder was and edit the source for MicroLua/lib/common/mlua.int64.c and
   put #include <sys/types.h> before #include <inttypes.h>. So anytime I
   pull in the source, I have to re-add this workaround. This is a known
   bug with cross-compiling the SDK on Ubuntu 24.04. Once I added the
   workaround, I can run the two cmake commands above and the compile will
   be successful.

   However, this bug↗ is not specifically for mlua.int64.c (as
   MicroLua is so obscure I found no mention of this), but it does provide
   an example of how this workaround was fixed for some other pico c
   modules, and I just adapted it.

I removed brltty, the braille display

   This was in case it tried to claim /dev/ttyACM0 for itself:

   sudo apt remove brltty

I had to add the right Pico board keyword

   In order to compile for the right board, the exact keywords pico,
   pico2, pico_w, and pico2_w need to be used, either set as environmental
   variable, or within set commands in the CMakeLists.txt file, or set as
   parameters to the first build command. The compile will error out if it
   doesn't match the exact board, something that caused me a lot of
   trouble when I was compiling for Pico 2 W using just "pico2" (which did
   not work).

I had to identify unknown dependencies

   Some functions may require a module that isn't specifically listed, so
   there is some experimentation determining what the exact dependencies
   are to both add in CMakeLists.txt and to add to your "require" commands
   at the top of your Lua program.

   Also the target_compile_definitions in CMakeLists.txt have similar
   dependencies. For example, if you set LWIP_DHCP=1 and compile, you'll
   get an error stating that you need to enable USB, so an LWIP_UDP=1
   fixes this. This makes sense since the DHCP protocol relies on UDP
   (like BOOTP), but you may have to experiment to get all the
   dependencies in if you encounter errors in compilation.

   One example of this is that if you use lwIP, you may get "In function
   NETIF_ip6:error: unused variable netif" compile errors if you don't
   have IPv6 enabled with LWIP_IPV6=1, and I've scoured the lwIP
   lwipopts.h possible module defines in MicroLua to see if there was any
   setting for NETIF or IPv6 to disable whatever module is depending on
   it, experimenting with several, but could not find a combination that
   worked (and I didn't go into the source to figure out where the
   dependency was), so I'm not sure if this is an lwIP issue or MicroLua
   so I had to leave it on. IPv6 should not be required just to use IPv4
   networking, so that's disappointing, but I can still block it at the
   router level.

   Another example is in the target_link_libraries. If you use
   mlua_mod_mlua.io.read, it can also use mlua_mod_pico.stdio if available
   (but this is documented).

Operation

   First the appropriate parameters in CMakeLists.txt have to be set, and
   then the program can be compiled as mentioned above. One has to be
   especially careful if using an original Pico W instead of a Pico 2 W,
   since it has half the flash and RAM, requiring some parameters to
   usually be reduced by half as well or it will crash or error out.

   Then the code has to be written to the flash.

   I use Picotool to prevent living like Desmond↗ where I have to
   pull the plug on the USB, push the BOOTSEL button, and plug it in again
   each time I want to drag a .uf2 file over. The compile will create .uf2
   and also .elf, and Picotool will nicely put the unit in BOOTSEL mode
   automatically (assuming Louia ObScura is already running correctly on
   it to allow it access) which makes future compiles to the unit easier
   to do remotely.

   But... picotool cannot tell the unit to restart over USB unless you
   have at least MLUA_STDIO_INIT_USB=1 set in your
   target_compile_definitions, which I do. And you also have to have the
   module loaded in your Lua file, which I do, and your program cannot
   crash or get stuck, or the underlying code will not be able to receive
   the command. Then you have to revert back to pushing the button,
   plugging it into USB, release button to ready it again for programming.

   Then to automatically put the Pico in BOOTSEL mode and flash the new
   program and run it, (assuming it is either already in BOOTSEL mode or
   later when Louia ObScura is up and running) it is just a matter of
   running this from within the louia directory:

   sudo picotool load --update --execute build/Louia_ObScura.elf
   --force-no-reboot

   Then, assuming Ubuntu 24.04 LTS with minicom, run minicom in a terminal
   as "sudo minicom -b 115200 -o -D /dev/ttyACM0'". Since it does not
   detect input, it will immediately exit. Now arrow up and get ready to
   launch that minicom command once more at just the right time:

   First plug the device into USB, and about a second later, press enter
   to run that minicom command. This time the window should not close,
   since the device /dev/ttyACM0 exists (and it won't close in the future
   if it disconnect temporarily once it connects). The device first pauses
   3 seconds before printing to the TTY (to allow the TTY to connect and
   be ready, configurable in the PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS
   variable in CMakeLists.txt). If the TTY catches it in time, it will
   show:

   Louia ObScura version 1.2 at your service.

   Note that if the program crashes before boot or there is a problem with
   the code, you will not see the /dev/ttyACM0 device being created at
   all--it just won't be there.

   But if you got this far, so far so good! Then it will detect first boot
   and create the config file. Just to make sure there was enough time for
   the TTY, it will then wait 10 more seconds before asking:

   This appears to be the first boot. Config file was created with
   defaults.

   Do you want to view or change the config (y/n)?

   Then press y within 7 seconds to enter the configuration. Note that if
   you already set your parameters in CMakeLists.txt beforehand, this is
   optional, as it has already copied those values to the config anyway.
   This is just if you want to change them later without having to
   recompile.

   But if y is pressed, the following prompt will appear if a config
   password is set:

   Please enter the config password (insecure and not encrypted):

   At this point, type in the password "louia" (or whatever you set it to)
   and press Enter. It will then show the list of the configurable
   parameters in the CMakeLists.txt file (except for the MicroLua specific
   or the ROM variables) and will then prompt for:

   Please enter the number of the parameter that you would like to view or
   change (d to delete config file, Enter to exit):

   At this point you would just enter the number of the variable you want
   to change and type in the new value. These values have to be exact and
   match the data type of the variable or the entire thing will crash and
   error out and you may have to nuke the flash and do the whole thing
   over. So edit parameters with caution. The cursor is slightly lower
   than the prompt, which is normal since my code is crude.

   If you hit Enter it either saves the value or returns to the list if
   left blank. That number can be entered again to check to see if it is
   saved correctly. When done, press Enter instead of selecting a number
   and it will proceed. You also have the option of pressing d to delete
   the config file entirely. If this is done, it will say "Config file
   deleted. Please restart device to restore default values." and then
   pauses for 1 minute to wait for you to unplug and plug the device back
   into USB. If you do not restart the device, after 1 minute, it will
   still proceed but will not use those default values until the next
   reboot.

   But once you leave the config menu (or if it auto exits in 7 seconds if
   you never originally pressed "y" in the first place), then since it is
   first boot, it won't find a file system on the data partition. It will
   then ask another y/n question:

   No data can be retrieved from this drive. Is it okay to format and
   create directories (y/n)?

   If this is the first boot, one should select y, and it will create the
   empty directory structure as defined in the MKDIRLIST colon-delimited
   string of directories. If the format is skipped, it will likely crash.

   The reason it asks and doesn't just proceed anyway is because a corrupt
   drive or a change to the MAXDATAPARTSIZE or MAXFLASHSIZE will also
   force a format, so it gives a last-minute chance to stop and pull out
   the device in case there was some critical data that still needs to be
   saved or restored (although no critical data should ever be stored on a
   non-encrypted Spartan server in the first place).

   Once formatted, it should say "Format successful." and then show a list
   of all of the directories and/or log file it created.

   If SAFEFILES is enabled (true) then it will also load the file array
   with these directories and say "File array is loaded."

   Then it starts up the Louia Spartan server for the DOMAIN and PORT
   indicated.

   It will say something like this, if the port 300 and the domain is
   "pico":

   Louia server started on port 300 for domain: pico

   To test, use: "telnet pico 300" then type "pico / 0"

   You don't have to test with telnet, this is just for my own debugging.

   However, since this is the first boot, the wifi check will discover
   there is no connection and will attempt to connect. It will say
   (hopefully):

   Trying to initialize CYW43

   Initializing wifi...

   Disconnecting from old wifi...

   Connecting to wifi...

   Link is up.

   Server IP is: 192.168.0.100

   Then if SAFEIP is set with an IP address, it will show:

   From client 192.168.0.101, to test locally if behind a firewall, use:
   "telnet picolocal 300" then type "picolocal / 0"

   This is just an example set of IP addresses. The server IP will either
   be returned by DHCP if the "dhcp" strings were set or it will be the
   static IP address configured. When SAFEIP is set to the IP address of
   the client the admin is using, the site can still receive requests to
   the DOMAIN, but it will also receive requests to a different
   DOMAINLOCAL domain which could be set via /etc/hosts file in Linux to
   redirect to that private IP (or localhost if using an SSH tunnel), a
   way to allow the admin to also use it from behind a local firewall
   without the need to send traffic over the Internet, since setting a
   hostname in /etc/hosts to match the actual name could interfere with
   any local SSH tunnel (and interfere with normal browsing to that
   domain). And, of course, telnet is the least-desirable way to browse a
   Spartan page, but it is good for troubleshooting.

   Personally, I like to use "static DHCP" (sometimes called DHCP
   reservations) and configure my router (which uses dnsmasq in my case)
   to always hand out the same IP address based on the MAC address of the
   device, although this may not be an option if one does not have access
   to directly administer the DHCP server, so I included a static IP
   option. If something goes wrong with the wifi connection, it will
   display the error and keep trying every few seconds. Note that my code
   is not robust and there are many situations where it does not
   auto-reconnect forcing the user to reset the power (unplug and plug the
   device back into USB) to reboot it. Obviously this is not ideal if the
   unit is in the field and hard to reach.

   But if the link is up, at that point the terminal stays quiet until a
   Spartan request is received or there is some other important event,
   unless DEBUG is enabled (true) in which case it reports back every 10
   seconds with some time values when it does some periodic checks/syncs,
   and floods the screen with input under other conditions.

   In the background the system cycles at high speed, running the main
   receive and dispatch loop and all of the functions of the Louia ObScura
   OS, including the Spartan server itself. Its cycle speed is limited by
   the TIMEOUTCHECKSECS parameter, somewhat similar to the original Linux
   based Louia server, except that in some case I found that I could not
   set the timeout to 0 for maximum speed or it would block some of the
   background functions needed for wifi and USB communication (which was
   not a factor in Linux, of course, since the Linux OS handled those
   lower layers). But adding a higher TIMEOUTCHECKSECS value, say 1 to 3
   seconds instead of the default .000001 seconds, while not ideal for
   actual use, does help in viewing the debugging information to see what
   is happening.

   So at this point the server is up and running, but there are no files
   to view unless LOGPATH was set to enable the log file, in which case
   one should be able to use a Spartan-capable browser like Lagrange or
   Offpunk (or telnet with the appropriate protocol syntax if you are so
   inclined) to view the log that increments every time the page is
   refreshed (since your own request is adding the the log). There will be
   no date at this point, just a roughly-precise but grossly-inaccurate
   minute:second counter, since it doesn't yet match real time.

   The next step is to upload site files so the Spartan Louia server has
   something to serve beside the log. The files should be placed in a
   Linux directory structure that matches those defined in the MKDIRLIST
   parameter, including the admin.lua and obscura.lua files that are part
   of Louia ObScura to view the optional scripting functions.

   I created a BASH script called upload.sh to do this that gets some file
   size feedback to make it faster than just forced timeouts.

   First it triggers the upload site mode using the standard Spartan input
   prompt, then it uploads them to a special port defined under
   UPLOADSITEPORT using a custom, non-Spartan but Spartan-like upload
   protocol, default 301 until it receives the final "done". This port
   should be only available locally, behind a firewall. Don't expose it
   for public access. I like to use an SSH tunnel for this such as:

   sudo ssh -L:300:picolocal:300 -L:301:picolocal:301 user@server

   This assumes the correct ssh configuration is done, of course.
   Nevertheless, the server only opens that port during the upload and
   closes it afterwards, and if SAFEIP is enabled, only allows uploads
   from the admin's IP address (so it will exit out and not perform any
   upload if the client IP does not match SAFEIP).

   Usage: upload.sh mode [path/to/file]
   Where mode is either "file", "rmfile", or "dir"

   The leading / should be omitted since it is in Linux directory and not
   an actual Spartan path at this point.
     * file means it uses uploads file in the second parameter.
     * rmfile means deletes the file in the second parameter (only from
       Louia ObScura, not the Linux file system).
     * dir means it uploads the entire current directory unless the
       CURRENTDIR variable inside the script is set to a different
       directory. No second parameter since it just uses the current
       directory.

   And there are 5 user-defined constants in the script that have to be
   set to match the same ones as in CMakeLists.txt. They are DIR,
   DOMAINLOCAL, PORT, UPLOADSITEPATH, UPLOADSITEPORT. And there is an
   extra one, CURRENTDIR, that is set to "" empty string, but if set to a
   full path (without the trailing /), it will use that directory path in
   lieu of the current directory. CURRENTDIR also applies to individual
   files and removals if set.

   Another way to delete a file on the Louia ObScura server that one also
   wants to delete in Linux would be to set that page to 0 bytes in Linux
   (e.g. touch mypage) before uploading it, and it will delete the
   existing page completely from the server. Then the 0 byte file would
   have to be manually removed in Linux as well. But I like the rmfile
   option better as it just works with the Louia ObScura server files.

   Note that the uploadsite process is very unreliable and will often
   freeze when doing a large directory transfer. If you exceed the
   remaining disk space it will freeze without warning and will require a
   reset and/or format. But often it just hangs when trying to transfer a
   filename or file data. In that case, the upload.sh script will not
   timeout but will freeze. At first it may seem like a large transfer is
   occuring, but after waiting a while it will be apparent it has locked
   up. At that point a CTRL-C will end it. But the server will stay frozen
   until it times out according to a TIMEOUTUPLOADSITESECS parameter,
   which is by default set to 90 seconds, at which point it will exit the
   uploader and resume server processing of incoming Spartan pages (which
   may be in a partial state due to the botched upload), and resume any
   coroutines that may be in progress that have not timed out (they are
   set by default to 120 seconds), as long as their sockets are still
   working (which can also cause a crash). I did hardcode a .1 second
   delay into the stages of the upload.sh script and this delay could be
   increased, although it will obviously slow upload times, and I haven't
   had consistent success when using delays.

   But if it miraculously completes the upload, one should be able to
   browse to the HOMEPATH (if it was uploaded) using a Spartan-capable
   browser like Lagrange or Offpunk (or telnet with the appropriate
   protocol syntax if you are so inclined) to view your Spartan site and
   pages. Or one can browse to the /scripts/admin.lua page (set at
   ADMINPATH) to view various statistics since SAFEIP had to be set to the
   admin's IP (or disabled) to upload it in the first place, so it should
   still allow the admin access. If you use SAFEIP but ever switch to
   another IP and it blocks you, then you'll need to reboot the device,
   enter the config again, and set SAFEIP to your IP (which should be a
   local IP behind a firewall and not one exposed for public access).

   At this point, the basic server should be operational, and everything
   else is just tweaking and configuring as needed. I like to keep two
   instances of the Lagrange browser open, one to view the site through
   the encrypted ssh tunnel under encrypted WPA2 wifi (the SAFEIP), and
   one to view the cleartext public path. If the device is unplugged and
   plugged back in, it will again pause the 3 seconds before display to
   TTY, then allow 7 seconds to choose to enter the config. It won't pause
   the additional 10 seconds anymore since it knows it is not the first
   boot. So if one does nothing, it will boot up and try to connect to
   wifi in 10 seconds, with the wifi taking about 5 seconds to connect, a
   total of about 15 seconds boot-up time before the site can be accessed
   via a browser, assuming no issues.

   There are many cases, though, where it can crash or the config file
   parameters become out of sync/corrupted if significant modifications
   are performed, requiring the entire drive to be nuked and reflashed (or
   even recompiled), so do not use this software unless you read the
   source and know what it is doing! It is only an experimental,
   proof-of-concept with lots of bugs.

   If the server is ever used for public access, one may want to add
   logging to see what kinds of request are coming in or confirm if it is
   operational (since there is no Linux server here to provide further
   analysis), but it will slow it down slightly and add additional wear to
   the flash since each entry is written using LittleFS copy-on-write
   mechanism, so every request read requires two writes to the same flash.
   And of course, logs are far more meaningful if you know what time the
   event occurred.

   Since I could not use the hardware RTC on the Pico 2 W, as mentioned in
   the Louia ObScura project page, I had to design my own RTC/NTP like
   system. So if logging is desired, the next step would be to have an
   external Linux server "push" or upload a special date and hour string
   to the server exactly once an hour, on the hour using a cron entry to
   provide real-time to the Louia ObScura server so that the times of the
   log entries are properly recorded. By default there is a 1-second
   offset set in TIMEOFFSETUS as 1000000 microseconds to account for cron
   and upload delays, which can be modified depending on situation. Here
   is an example of a BASH script called rtc.sh that is run by cron run
   once each hour, assuming the TIMEPATH is set to /settime (which should
   be changed), and assuming the LOCALDOMAIN is picolocal and the PORT is
   300. Ideally the LOCALDOMAIN would be listed in the /etc/hosts file to
   resolve to the private IP of the server (not the public IP or the
   TIMEPATH would be exposed for a replay attack).

   #!/bin/bash
   /usr/bin/date=`date '+%Y-%m-%d %H'`
   /usr/bin/echo -n -e "picolocal /settime 13\r\n${date}" | /usr/bin/nc -N
   -w 1 picolocal 300

   The /settime path above should be set to something random and unique to
   match the corresponding TIMEPATH value in CMakeLists.txt and kept on a
   private local network.

   And cron would run it once an hour like this, assuming the path to the
   script is /rtc.sh and it is set to executable:

   0 * * * * /rtc.sh > /dev/null

   If it doesn't receive the hourly updates, the entries will just say
   "YYYY-MM-DD 00" for the date and hour, and the minute and second
   entries will keep looping around using a modulo↗, never
   incrementing the hour or date, and not matching actual real-time
   minutes (acting merely as a counter) unless you happened to boot the
   server on the hour. The RTCIP value also has to be set for the IP of
   the server that will be sending that date upload, as mentioned earlier.
   But if successful, the log will show an upload once an hour with the
   time string. Interestingly, if the time upload is received right on or
   after the 00 minute mark (which it often does), the upload time stamp
   will show the hour before the date and hour upload was received, since
   the minutes rolled back to 0 just before the upload was received, but
   any subsequent logs will be accurate until the next date and hour
   upload. So the time of the time upload itself is often showing as
   1-hour behind.

   So now that the Spartan server is fully operational on the Pico 2 W, or
   even the anemic Pico W, there are some fun things to try:

   The admin page can also use some input prompt/upload parameters. For
   example, "ls" performs a directory listing. In Lagrange, it is easy to
   just type ?ls at the end of the admin page path in the URL field and it
   will respond immediately, akin to a graphical Linux terminal, but over
   Spartan. Spartan's hyperlinks aren't exactly Web 2.0 RESTful apps, but
   they do have web-like and CGI feeling that leverages the convenience of
   hyperlinks to pass data, and the server dutifully performs back-end
   processing. Does this potentially open up security holes? Most
   definitely--remember the 1990s? But it can be turned off by setting
   SCRIPTSENABLED to false.

   One can also test and run the sample /scripts/obscura.lua script (set
   by default in TESTSCRIPTPATH) which shows off the power of the Louia
   ObScura multitasking OS to take input, process the input and even
   output it to its own .pid page while the Spartan server continues to
   serve files. That .pid page can even prompt to accept the input of
   another .pid's output in real-time, stringing together multiple
   cooperative processes without any locking issues. It's also fun to
   replace the .pid extension on the PID file accessed with an .rip
   extension to kill the process in real-time, along with adding a .del
   extension to delete the .pid file completely. Then one can check the
   admin page to see how many coroutines are still running, what the
   memory use is, and how many files are still out there, needed
   visibility since there is no Linux OS in this dim and alien Land of
   ObScura.

   Then one can write and upload additional scripts using the 3
   rudimentary API functions:
     * pidprint(text) - This writes output to a unique .pid file on the
       flash drive, creating the file if it doesn't exist. You don't have
       to use pidprint() and can just return the output back to the user
       immediately like CGI, but it gives the option for persistent
       computation or batch like style.
     * text = pidinput() - This reads uploaded input in real time from
       other Spartan upload requests to that .pid page.
     * text = pidread(pid,startbyte,length) - This reads a length of bytes
       from an existing .pid page, starting at the start byte.

   Of course, the commands are extremely limited and additional
   script_environment table entries in the source code under "DEFINE
   GENERAL SANDBOX" comments heading would have to be added for additional
   functionality as needed, even more so if SAFESCRIPT is enabled which
   prevents it from accessing the _G Global Environment. But if you're
   fluent in Lua, you may find this proof-of-concept small, Lua-based OS
   interesting (and can point out the multiple deficiencies in my code as
   well). But it's easy to poke holes in (or fun at) something once it is
   created and in front of you; it's much harder to manually create
   something working to this extent and functionality from scratch out of
   thin air (and document it) within a 3-month span in your spare time
   (about 4 months total if you include the Louia code, too).

   I won't go into further detail here; all is revealed in the source code
   to the careful reader (hopefully I commented it clearly enough), along
   with the admin and example scripts, and along with the additional
   details in the project page and the original Louia project
   documentation. This is intentionally not a how-to for those that just
   want a consumer device (which this is not).

Known Issues

     * A BSSID MAC address is saved as a string of hex bytes in the
       CMakeLists.txt file, but when deserialized and parsed, those bytes
       could collide with the delimiters, so certain hard-coded MACs
       addresses will fail unless DHCP is used and that field is left
       blank
     * There was no easy way to add sandbox SAFESCRIPT command lists for
       general and admin use, so these lists are hard-coded. It is only
       proof-of-concept at this time.
     * The uploadsite() function hardcodes 512 byte chunks with 3-second
       timeout and only uses file sizes in conjunction with the upload.sh
       script. Only file sizes are checked by the upload.sh script, there
       are no checksums and it can crash or hang at times. The Linux
       upload.sh script uploads uses /tmp tmpfs ramdisk-type files rather
       than BASH FIFO which it should have done. It also opens and kills
       netcat over and over, which is not very efficient, and it has no
       timeout function and would have to be killed with CTRL-C and then
       wait for the server to timeout.
     * The priority() function is poor quality and something I just
       created quickly to get it working for my testing and demonstration.
     * The lwIP TCP transfers are okay, but mainly use timing and
       heuristic hacks to get some reliability. They are not that reliable
       and a bit slow.
     * The code is not streamlined, and there is a lot of program logic
       that is unnecessarily large. I also put too many conditional
       negations at the end, causing large indentations, but should have
       put them at the top to break out early. There are too many "if
       DEBUG" statements that slow it down if debugging verbosity was
       never desired.
     * There are no error messages when Lua load() scripts crash inside a
       coroutine under pcall() as I haven't put time into capturing them.
       There is little error checking in general, and I didn't use enough
       assert() functions which would have also simplified my work.
     * If editing the live config on boot, the parameters are updated
       immediately, but if you delete the config file, they will not go
       into effect or allow you to add live updates until you reboot. But
       software rebooting is difficult on the Pico series unless you use
       the watchdog timer or access the C API so I haven't put in the
       effort yet. There is little and sometimes no input validation when
       inside the config menu on boot.
     * I didn't yet turn on the new generational garbage collection in Lua
       5.5 or try out the new 5.4 finalizer metamethods to save memory.
     * The directory structure is very basic and uses inconsistent
       conventions. You have to use a main directory DIR as the site
       directory, but then everything else is a path that can also contain
       further directories, like /my/path. Even the log directory is atop
       the DIR directory.
     * The admin page and PID files should ideally be stored in either the
       config partition or in RAM disk, but this was not done and they are
       still in the main partition in the DIR directory along with the
       other files
     * The FastCGI like Lua scripts that run use PID numbers that are not
       randomized, so may be easy to guess and can also collide with older
       PIDs that are no longer running but left a .pid file behind. They
       just use the coroutine hex address upon creation as their unique
       ID.
     * The MIME and NICE tables were too troublesome to add to the
       CMakeLists.txt file, so they are left defined directly in the code.
       I don't think MicroLua's use of CMakeLists.txt understands Lua
       table constructs, and I didn't feel like serializing and delimiting
       strings instead.
     * Variable scoping is not very strict and can be improved.
     * The parameter assignments are overly complex. This was to match the
       type of variables used in Louia while allowing them to be
       displayed, modified on the fly, and saved into a local config file
       in order if needed. And since I make them individual variables
       instead of put them in a key/value table, I cannot pass them by
       reference to the load() function to allow the admin page to toggle
       parameters on the fly (which would have been nice). The
       script_environment table values passed to scripts also has to be
       hardcoded in the middle of the source code which is unweildy when
       changing values. If you make a mistake typing in a value and have a
       space in there, you won't be able to see the space and won't know
       why the behavior doesn't match the settings (which happened to me
       and took me 2 hours to resolve).
     * There is quite a bit of duplicated code that could be turned into
       functions. I was trying to minimize functions like I did with Louia
       but on the Pico, I had to write so many routines to compensate for
       the lack of an OS that the code sprawl got fairly significant.
     * Not all parameters were able to be added to CMakeLists.txt file,
       unfortunately, as tables were unwieldy to serialize/serialize via
       strings and not collide with delimiters.
     * There are too many wasted CPU cycles doing time arithmetic in live
       code
     * Lua is supposed to use nil as the value of an undefined variable.
       But... there are are cases when evaluating an undefined variable
       will not return nil but will crash with undefined symbol! This is
       apparently because MicroLua is tied closely into underlying C
       functions. An example is trying to open a LittleFS file as f and
       then performing an "if f" to see if f evaluates to something true
       and not nil. It will crash if that file is not found. I likely
       didn't handle all of these special cases carefully enough and fixed
       them as I encountered the crashes.
     * There is obviously no firewall--the port you see is the port you
       get--so is best to run behind another firewall, which I do.
     * Unique admin paths are shown in the log if LOGPATH is set to enable
       logging. Ideally, these would be random strings that nobody knows
       to prevent use by others. So having them in the log, like the other
       paths, exposes them to anyone viewing the log, although the log
       itself cannot be read by others over the Internet if SAFEIP is
       enabled and is accessed from behind a firewall on a switched local
       LAN where the cleartext traffic cannot be picked up. So if SAFEIP
       was disabled and logging enabled, anyone that knew the LOGPATH (or
       the ADMINPATH which allows you to see the LOGPATH) could eventually
       find the other paths (such as the FORMATPATH) and wipe out the
       server. If SCRIPTSENABLED and UPLOADSENABLED are false, the admin
       page in ADMINPATH would be blocked, but not the FORMATPATH,
       RELOADPATH, CLEARLOGPATH, UPLOADSITEPATH, or DELETEPIDFILESPATH.
       Removing them from the log wasn't that advantageous (since its only
       security relies on secret paths and a private LAN, security through
       obscurity), but the combinations have to be carefully chosen. These
       paths will also show up on the default .lua script pages like
       admin.lua and obscure.lua (which should only be used by the admin)
       and are neither hidden nor encrypted. Like amateur radio, the
       server was neither designed to serve nor process private, encrypted
       information and only has basic restrictions for administration
       purposes. A downside of this approach, though, is that if you want
       to test over a public network to see if your security is working,
       the test itself will compromise the security. So I use fake values
       to perform the tests, then later change them to real values (and
       refrain from further security testing over public networks).
     * The logs, if enabled, write to flash. Even though the size is
       capped, write endurance is always an issue with floating-gate
       MOSFET flash memory technology. LittleFS, while it has
       wear-leveling, also does Copy on Write and increases writes. This
       is exacerbated by the fact that they work with such little free
       space that they have no choice but to write to the same locations
       frequently. But this is not an industrial, hot-swappable RAID
       system, and the cost to replace the entire Pico (along with its
       flash) is insignificant (and quick). Keeping some spare ones on
       hand, like I do for the Pi Zeros, is one solution, and the fact
       that Spartan is still an obscure protocol helps.
     * The links in the the administration console in the admin.lua script
       are too touchy using the Lagrange 1.18.8 browser and can be
       accidentally clicked (which I've done many times), instantaneously
       formatting the drive, since you don't have to hover exactly over
       the link to select it but just the row, which extends far past the
       link to the right. The ps and ls commands also require more memory
       to run depending on the number of coroutines or files found.
     * The config TTY screen menu gets cut off on the right if the
       terminal window is not sized wide enough.
     * Turning SAFESCRIPT off usually hangs the scripts since having full
       _G access to global environment table appears to create problems
       with my code.
     * I didn't print the MAC address on boot or via the admin console, so
       the only way to fetch it to see what it is in case static DHCP is
       used is to get it from the router. The function of the BSSID string
       is unclear and I'm not sure if this customizes the MAC or just
       sends the current MAC, as I didn't get expected behavior so just
       left it the same as the physical MAC.
     * I should have added mode information and a refresh link to
       admin.lua admin console. For example, it would have been nice to
       have seen if STRICTMODE or DEBUG was true or false from the console
       instead of having to reboot and look at the config options each
       time. The disk free is inaccurate and you can't reach 100 before it
       locks up.
     * Variables that I gave the option to unset, like SAFEIP, RTCIP, and
       LOGPATH if not needed could not be set to "" empty string since
       they would not pass through CMakeLists.txt properly into MicroLua,
       so I used the words "any" for the first two and "none" for the log
       as options to disable them, but that confused my program logic, so
       there is unexpected and unwanted behavior when not setting them to
       an IP (or a path in case of the log).

Disclaimer

   Warning, this project is experimental and not recommended for real data
   or production. Do not use this software (and/or schematic, if
   applicable) unless you read and understand the code/schematic and know
   what it is doing! It was created by a human (myself) and not AI, and I
   made it solely for myself and am only releasing the source code in the
   hope that it gives people insight into the program structure and is
   useful in some way. It might not be suitable for you, and I am not
   responsible for the correctness of the information and do not warrant
   it in any way. Hopefully you will create a much better system and not
   use this one.

   I run this software because it makes my life simpler and gives me
   philosophical insights into the world. I can tinker with the system
   when I need to. It probably won't make your life simpler, because it's
   not a robust, self-contained package. It's an interrelating system, so
   there are a lot of pieces that have to be running in just the right way
   or it will crash or error out.

   There are all kinds of bugs in it, but I work around them until I later
   find time to fix them. Sometimes I never fix them but move on to new
   projects. When I build things for myself, I create structures that are
   beautiful to me, but I rarely perfect the details. I tend to build
   proof-of-concept prototypes, and when I prove that they work and are
   useful to me, I put them into operation to make my life simpler and
   show me new things about the world.

   I purposely choose to not add complexity to the software but keep the
   complexity openly exposed in the system. I don't like closed,
   monolithic systems, I like smaller sets of things that inter-operate.
   Even a Rube Goldberg machine is easy to understand since the
   complexities are within plain view.

   Minimalism in computing is hard to explain; you walk a fine line
   between not adding enough and adding too much, but there is a "zone", a
   small window where the human mind has enough grasp of the unique
   situation it is in to make a difference to human understanding. When I
   find these zones, I feel I must act on them, which is one of my
   motivating factors for taking on any personal project.

   Here is an analogy: you can sit on a mountaintop and see how the tiny
   people below build their cities, but never meet them. You can meet the
   people close-up in their cities, but not see the significance of what
   they are building. But there is a middle ground where you can sort of
   see what they are doing and are close enough to them to see the
   importance of their journey.

   The individual mind is a lens, but, like a single telescope looking at
   the night sky, we can either see stars that are close or stars that are
   much farther away, but we can't see all stars at the same time. We have
   to pick our stars.

   I like to think of it like this:

   It is not within our power to do everything, but it is within our
   power to do anything.
     __________________________________________________________________


