QZ qz thoughts
a blog from Eli the Bearded
Tag search results for blog Page 8 of 21

Cut-and-paste as a scripting language


I'd say one, perhaps controversial, technique that has been very successful for me in computer system administration are scripts that are not intended to be run with a shell or other single tool but as a series of things to cut-and-paste. I frequently use such things in situations like:

  1. Drafting a proper scripts to be run from Puppet, Chef, Ansible, cron, etc.
  2. Massaging existing systems from one state into another before Puppet, Chef, or Ansible takes over management. This includes bootstrapping.
  3. Changing state in things not under Puppet, Chef, Ansible, etc, control, because, eg it's a database not an OS.
  4. The code is to be used as a runbook for what should be done in the case of an error.

In many cases the cut-and-paste script is something that should become a proper script if it is going to be done often enough, but see point one. A number of systems that need to be brought into a consistent state but for whatever reason are starting from a diverse set of existing states might need a script with a lot of branching. The cut-and-paste script has a human running it and human that can perform error recovery and new branch configurations with relative ease.

To point two: in some cases there will be enough systems to perform this on that some script is required, but the test to know which state should apply is a complicated one to script and it's much simpler to let a human decide the steps needed after which the test becomes simple and automation can take over.

And for point three: always you will have some aspect of the system that is supposed to be above the level of the automation but for which a degree of control is sometimes needed.

Lastly point four: a runbook that provides as exact as possible set of steps to follow allows for more error-free recovery from issues when they do arise. Sometimes this set of steps can be shown to be reliable enough to create scripts (point one again) that perform autorecovery.

I think at this point it becomes clear that the cut-and-paste script is a useful developmental tool for creating robust automation. At least, I frequently find it to be so.

Some examples of situations I've used cut-and-paste scripts:

  • I first developed this method managing a fleet of web servers in the early '00s. The site and the config were all in source code control, but triggering syncs and Apache config reload were manual actions. A loop like for host in $(< hostlistfile); do ssh $host "cd /webroot && p4 sync"; done would work, but it wouldn't be nearly as close to simultaneous as opening sixteen xterms each sshed in to a web server (easy to locally script) and then pasting the commands as fast as possible in each. Later (much later) that company started to use Chef, where the knife command could do a good job of replacing such stuff.
  • Using a web browser to connect to "console" of a newly installed system, using xdotool to type the commands to bootstrap networking onto the system. That "console" was some weird javascript app that didn't accept "paste", hence getting creative with xdotool to emulate typing in it. That network had no DHCP and needed a static IP on the correct VLAN before it could connect. I broke down the commands into several xdotool commands for two reasons, (a) to keep the command lines from getting unreasonably long (ie personal taste), (b) to not "typeahead" when starting programs like a file editor, and (c) to not have to encode which static IP to use, instead just getting right to that point, entering it by hand then continuing with the script. Finally the script ended with rebooting, and then I could take over with ansible, the config management tool of choice there.
  • Filling out a number of web forms where an API should be used, but there is resistance to making the API usable. Specifically, of late, that has been managing "silences" during system updates in Prometheus Alertmanager. Due to the login being behind Okta, command line tools can't authenticate. There is a proposed fix for this, but it hasn't been prioritized yet. In the meantime, I'll just open vi in an xterm and put field values to use on separate lines for quick triple-click copying. Typically I'll have two files open one with the things that are the same for every "new silence" and one for the hostnames changing between them.

One thing that has helped with all of this is the standard X11 paradigm of "select is copy" and "mouse middle button is paste". I can double click on a word in one window, move the mouse to another window and paste it, with no keyboard. Or triple click for line, and then paste it in as many windows as I want with just a single click each. It becomes the opposite of hands never leave keyboard, where the script run is completely done with hand never leaves mouse (at least until the script needs an edit). This cut and paste paradigm never caught on, and it makes me less productive on Macs and Windows. Macs are in some ways the worst, because it's Unix but it's not X11: almost, but not quite. (And to add to the pain, Mac keyboard shortcuts are nothing like xterm ones.)

Of course, if you do need to type some part of the cut-and-paste script learning and using keyboard shortcuts for that, particularly paste, are the way to go. Consider this simple script:

for host in $(< hostlist ) ; do
        # need to set EDITOR enviornment variable after sudo
	ssh -t $host "sudo env EDITOR=ex crontab -e"
done
# paste to ex:
#	:g /day$/ s, \* /, 1-5 /, | x 

One can copy that global command, which finds lines ending in "day" and edits the * before the / to be 1-5. For a crontab like:

MAILTO=cron-list@example.com
3 9 * * * /usr/local/bin/start-day
10 2 * * * /usr/local/bin/run-backup

This will change run every day at 9:03 am start-day program to only run on weekdays: 3 9 * * 1-5 /usr/local/bin/start-day and save the file. The for loop will log in to each host in the list of hosts, possibly prompting for an ssh password (depending on how you have that set up) and probably prompting for a sudo password (depending on how you have that set up). It would be quite reasonable to run the loop, enter passwords by hand, then hit <shift-insert> to use the default xterm paste primary keybinding to paste the script whereupon cron is updated, you are logged out, and move on to next host, so you end up just typing password<enter><shift-insert> password<enter><shift-insert> password<enter><shift-insert> ....

Some tricks of the trade that I've picked up over the years of doing this:

  • Many things that don't immediately look cut-and-pastable can be made so by rethinking them. File edits with ex or ed are easier to script than vi; xdotool can be used to control things in windows.
  • Whole lines are easier to copy than fragments, triple-click will select a line.
  • Learn exactly how to include or not include trailing new lines in your copies, it can make a lot of difference.
  • Use variables like you would in a regular script loop for things that will vary from run to run, even if each run is on a separate host. This lets you separate variable setting pastes from command run pastes.
  • Setting variables with shell commands instead of manually makes for a smoother experience. grep, head, and tail, are good for selecting lines from output. cut, sed, and grep -o are good for selecting part of a line from output:
       host=$(hostname -f)
       homeuse=$(df /home | tail -1 | grep -o '[0-9]*%' | cut -f 1 -d %) 
  • Some shell line break methods are more fragile than others. Consider:
    # Pasted as a group, both of these run, even if you only want the
    # second to run if the first succeeds.
    /bin/false
    /bin/true
    
    # Pasted as a group, the second will run only if the first succeeds
    /bin/true &&
    /bin/false
    
    # The backslash will escape the following whitespace character.
    # If you have a trailing space, that's the whitespace that will
    # be escaped, not the newline.
    [ -f "$somefile" ] && 
    	head "$somefile" \ 
    	| next command
    
    # Written like this, trailing whitespace is ignored, the pipe
    # alone is enough to keep the shell looking for more to do.
    [ -f "$somefile" ] && 
    	head "$somefile" | 
    	next command
    

And remember, cut-and-paste scripts are a good way to write a real script, slowly dealing with all the error cases to handle and best ways to make the code universal. Use them to compose your startup scripts, your cron jobs, your configuration management tooling, your runbooks, and coding your self-healing systems.

Covid-19, Day 244


Latest numbers: dispensing with the state numbers now. US overall now over six million cases and more than 183,000 deaths. As "back to school" kicks in, new outbreaks, particularly at colleges are appearing. World wide cases are over 25 million and deaths stand over 844,000. In recent weeks some states have started to scale back testing, following Federal guidance that seems based on Trump's fewer tests will mean fewer cases fantasy. Rapid testing is still a burden to find in the US.

Increasingly I'm seeing bars and restaurants put in outdoor seating, using the "parklet" model San Francisco has. Basically this is the conversion of on-street parking to protected space for people.

Three parklets for outdoor seating

Here three separate businesses have three parklets at one intersection. One is simply metal barriers for now. If it follows the practice seen elsehwere, the walled wood platform will be there soon.

Prior to covid-19 very few parklets were all wood construction, and some had very artistic designs. Consider the "Deepistan National Parklet" created in 2011, with a topiary dinosaur.

The triceratops topiary at Deepistan Parklet

When it was created, Deepistan National Park(let) was the only parklet sponsored / in front of a residence.

But now rapid and cheap is the name of the game, and basic wood boxes are springing up around the city. This is all fine and well on good days, but rainy weather will be coming, and right now San Francisco is in the midsts of it's longest ever streak of "Spare the Air" days, where certain activities are discouraged and some are illegal due to bad air quality. (Discouraged: driving, at risk people spending time outdoors; illegal: wood fires including beach bonfiles.) The air is so bad because of blown in smoke from wildfires around the state.

It's a bad year for wildfires. The current batch was started by a rare series of August lightning strikes, months after the last rain. Many small fires started and grew until they combined into a few huge fires. Two out of the three largest wildfires in California history were burning simultaneously, and Covid-19 hindered both response and shelter options for people evacuated.

There have been a few days that the smoke layer sitting on the city has been very unpleasant for me. As I've gotten older, wood smoke causes my nasal passages and lungs more and more distress. When mask wearing started for Covid-19, I first began using my "Vog" brand N-95 mask which I had purchased for dealing with wildfire smoke in the city in previous years.

But more distressing than than the disruption of the disease or the suffocating smoke has been the politics. Trump's rejection of reality and outright stoking of racism, his obvious attempts to thwart voting, his destruction of norms and the US Postal Service, all of that is becoming a dispiriting drain on my mental health. Trump didn't accept the election results when he won, I have no illusions he'll accept a loss this time. And I have no stomach for imagining how it will shake out.

Game Tools


Here, some discussion of two game tool programs I have in game-tools on github.

asciimapper

In the mid-1990s, I knew an admin of the Tsunami MUD and played the game a bit. Fast-forward a decade and I decided to give it a try again. At (then) about fifteen years old (now closer to thirty), it was one of the older MUDs around, which meant it had a very long time to expand. There were vast areas of the game to explore, and I set out to see as much as I could.

Over the course of several months, I visited huge swaths of the game, and got myself on the explorer leaderboard, where I was one of the lowest level characters there. (Accounts automatically delete after time time if you don't log in, so I can't know if others had done better than me before then, and you won't be able to find me there now.) Eventually I started to run into time-to-new-area payoff diminishing returns and stopped playing.

While I was playing I drew myself a lot of maps. At first these were on paper, but eventually I developed an ASCII art short hand. This let me have text files I could grep for noteworthy items or places. From there, I wrote a tool that could take my ASCII art maps and convert them into nice printable maps. asciimapper worked by converting my ASCII art into config files for ifm the "Interactive Fiction Mapper", which was designed for Infocom and similar games. The crossover to MUD maps was trivial. Some of the maps I printed and would hand annotate for further details, but most I kept only in ASCII file form.

I have all my ASCII art maps for Tsunami somewhere, I could probably dig them out and put them on the web. I haven't played in at least a decade now, though, and there's more than zero chance some of them are obsolete. Some became inaccuate while I was playing. In particular I recall the entrance to Toyland moving, to be friendlier to low level players.

I've been thinking about asciimapper again as I play "Andor's Trail"; (previously dicussed about a month ago here). In "Andor's Trail", there are perhaps 520ish visitable areas, most of which show up on the World Map, but about 20% are indoors, underground, or otherwise not visible there. How to get to those plus the inventories of stores in particular spots has been something I've been mulling over. The ASCII art needed for the World Map would be doable, but something of a challenge.

The maps are text form already though, just not very clear text form. Here's an excerpt from AndorsTrail/res/xml/woodsettlement0.tmx, an XML file apparently created by Tiled:

 <objectgroup name="Mapevents">
  <object name="east" type="mapchange" x="928" y="224" width="32" height="64">
   <properties>
    <property name="map" value="roadbeforecrossroads2"/>
    <property name="place" value="west"/>
   </properties>
  </object>
  <object name="woodhouse1" type="mapchange" x="608" y="288" width="32" height="32">
   <properties>
    <property name="map" value="woodhouse1"/>
    <property name="place" value="south"/>
   </properties>
  </object>
  <object name="woodhouse2" type="mapchange" x="640" y="128" width="32" height="32">
   <properties>
    <property name="map" value="woodhouse2"/>
    <property name="place" value="south"/>
   </properties>
  </object>
  <object name="woodhouse0" type="mapchange" x="224" y="256" width="32" height="32">
   <properties>
    <property name="map" value="woodhouse0"/>
    <property name="place" value="south"/>
   </properties>
  </object>
  <object name="sign_wdsetl0" type="sign" x="800" y="256" width="32" height="32"/>
  <object name="sign_wdsetl0_grave1" type="sign" x="128" y="160" width="32" height="32"/>
  <object name="sign_wdsetl0_grave2" type="sign" x="128" y="224" width="32" height="32"/>
 </objectgroup>

You can easily see how the map pieces connect together, including ones like woodhouse0, woodhouse1, and woodhouse2 that don't show up on the World Map. In woodhouse2.tmx we find Lowyna:

<objectgroup name="Spawn">
  <object height="96" name="smuggler1" type="spawn" width="96" x="32" y="96"/>
  <object height="128" name="smuggler2" type="spawn" width="96" x="128" y="96"/>
  <object height="32" name="lowyna" type="spawn" width="96" x="288" y="96"/>
[...]

Which with a little bit of work we can connect that the shop "droplist", in this case in AndorsTrail/res/raw/droplists_v070_shops.json, to get items she stocks.

A map.tmx to IFM format converter might be handy, but I haven't put any serious thought into it.

asciimapper

I have thought about game play efficiency with "Andor's Trail". In particular while playing I thought it would be useful to have a way to see how fast I'm earning in-game rewards like XP, game currency, item drops, and how fast I'm using consumables while doing so. I imagined a tool that I could tell what I have at a particular time and it would work out how much that changes over time.

Those imaginings lead to stat-timer, a CLI with a very old school interogation interface. You can use the command line to give it starting stats or just start it and it will ask for stats. Then you can update as many or as few stats as you want each round and it gives updates. The design requires that you name stats for the initial state, and then if in same order, you can omit names. Thus the most important things being measured should be first, and least important last. Or least changing last.

In practice this means I've been putting XP first, then common area item drop and/or gold, then health potion count, and then rare drops, and finally — sometimes — constants I want for annotations. As I play, I update XP frequently and other columns less frequently. To update just the first two columns is a matter of just entering the first two numbers. To update first and third requires labeling the number for the third column. After each entry it gives a snapshot of how things are doing on a per-second basis. When done, I can <ctrl-d> out or put a ! at the end of the numbers to indicate final update. It then gives a final update with total changes, per-hour and per-second rate of changes. This makes it easier to compare play style one to play style two even if they are on different days and for different lengths of play.

If I update it further, things I've been thinking about for improving it include: a curses interface with data at particular screen locations, sophisticated "pause timer while entering data", realtime per-second updates, and perhaps a more sophisticated state model for the command line, for better continuation after an intertuption.

ascii-art