Installing NixOS on an ASUS EeePC 701

Posted by Craige McWhirter on

Those of you familiar with the ASUS EeePC 701 will be aware of it's limited memory (512M) and small internal drive (4G) which doesn't render it useless but does reduce it's modern usage.

I have two of these still in perfect working order and wanted to extend their functional lives. I have no need of any more routers and there are Raspberry Pi's everywhere here. I do have kids though and decided these would make great, ready made portable retro gaming consoles.

This blog post details how I installed NixOS and configured the 701's to overcome their short comings to extend their life.

At a Glance

  1. Download the latest NixOS minimal installer for 32-bit Intel/AMD.
  2. Boot off USB
  3. Set forcepae in Grub
  4. Connect networking
  5. Partition then format the SD card as / and the internal drive as swap
  6. Run the configuration builder
  7. Copy across nix files
  8. Install
  9. Enjoy

Process Detail

Have a copy of the NixOS manual handy. It will also be at [ALT]+[F8] once the installer has booted.

Booting

  1. Download the minimal installation CD, 32-bit Intel/AMD image and burn it to a USB stick.
  2. Insert the SD card and USB stick into the 701 (I used a spare 8G SD card I had handy).
  3. Power the 701 on and press [ESC], select the USB stick as the boot device,
  4. [TAB] at grub menu, append forcepae
  5. Continue the boot process until it reaches a root prompt
  6. If you have not connected Ethernet, connect to WiFi using wpa_supplicant, as per manual.

Partitioning and Formatting

With only a little deviation from section 2.2.2. Legacy Boot (MBR) we format the SD card to be the root disk and the internal drive as swap:

  • Create an MBR on the internal drive:

    # parted /dev/sda -- mklabel msdos

  • Create the root partition on the SD card:

    # parted /dev/sdc -- mkpart primary 1MiB 100%

  • Create the swap partition on the internal drive:

    # parted /dev/sda -- mkpart primary linux-swap 1MiB 100%

As per section 2.2.3 Formatting we now format the drives appropriately.

  • Format and label the / partition:

    # mkfs.ext4 -L nixos /dev/sdc1

  • Format the swap partion:

    # mkswap -L swap /dev/sda1

Installation and Configuration:

Now you can just continue to follow section 2.3 Installing and configure the system as you desire. I initially use a bare bones install and it can be read here. It's worth taking a brief look at if you're new to NixOS.

The section worth copying is related to how the kernel uses memory on this old i386 system:

  nixpkgs.config = {
    packageOverrides = pkgs: {
      stdenv = pkgs.stdenv // {
        platform = pkgs.stdenv.platform // {
          kernelExtraConfig = ''
            HIGHMEM64G? n  # 32-bit proc with > 4G RAM
            HIGHMEM4G y # 32-bit # proc # with # =< # 4G # RAM
          '';
        };
      };
    };
  };

Do not copy my import of retro-gaming.nix, that's a for after a successful install and a future blog post.

Once you've completed the install, the EeePC 701 ought to reboot into NixOS successfully.

Happy New Workbench

Posted by Craige McWhirter on

For the summer solstice I got a brand new second hand stainless steel bench top. Which by coincidence turns out to be the answer to the question "What do you get the man who has everything", apparently.

Brand new second hand stainless steel bench top

Turning to my cliché handbook, it turns out that when life gives you bench tops, you make workbenches. I needed a second standing desk for out on the deck and for hardware hacking, so under the watchful eye of a pair of local King Parrots I gathered some spare timber to cobble together a standing workbench for the bench top.

Stainless steel workbench upside down

Stainless steel workbench bare

Stainless steel workbench stage 1

There's a lot more work to be done on that desk but with stage 1 at least completed, it was time to install it on the deck, ready to hack 2019 away :-D

Stainless steel workbench in place

Yes, that is the Australian Sun reaching in, in the background to kill us all.

The astute amongst you will have also noticed the (empty) Lagavulin and Talisker bottles propping up my laptop so they're both physically and metaphorically supporting my work.

Looks like I've already put my hand up for some hacks on Termonad to be the first piece of work completed from the new deck desk, apart from this post of course :-D

So now that leaves me with an inside and an outside standing desk as well as a hammock to work from. Not sure what says about me, I'll leave that up to you.

Book Review: Fear: Trump in the White House

Posted by Craige McWhirter on

by Bob Woodward

Fear: Trump in the White House

If you suffer from anxiety about what's happening in the world and the competence of those in power, this is probably not the book for you. Bob Woodward's book shows in startling detail just how inadequate, incompetent and immature not only the President of the USA is but also most of the team that he had/has around him.

I didn't takes notes as I read this book and it really needs it. There's so much going on that its difficult to know where to begin. I can understand why Bob wrote this in a pretty much chronological fashion. There's one quote that stands out from Dowd, Trump's personal lawyer, the man that I came away with feeling had Trump's best interests at heart more than anyone else:

But in the man and his presidency Dowd had seen the tragic flaw. In the political back-and-forth, the evasions, the denials, the tweeting, the obscuring, crying "Fake News," the indignation, Trump had one overriding problem that Dowd knew but could not bring himself to say to the president:

"You're a fucking liar."

The primary feeling I was left with after reading this book was arrested development. This phrase best describes for me the behaviour of most of the people in involved in this tale. They just are not mature adults with a steady hand.

It's a phrase I've also come to associate with Australian politicians like Tony Abbott, Peter Dutton, Pauline Hanson, Clive Palmer... I'll stop there, it could be long list and there are not many Australian politicians that I could leave off that list sadly.

The kind of people who put their own narrow interests or the interests of their base or more importantly, donors, ahead of clear eyed long term national interests. The public rightly becomes disengaged with politics which leads to our politicians being drawn from an increasingly shallow pool of staffers and cronies, which leads to increased public disengagement, which further shrinks the pool from which our politicians are drawn.

It's a vicious loop that Wolfgang Streek expounds upon quite well in How Will Capitalism End? : Essays On A Failing System , a highly recommended read that looks deeply into and explains the socio-political problems of the modern era.

The secondary feeling I'm left with is just how broken "western" democracy truly is (I've argued at times that we do not actually live in a genuine democracy, just because we get to vote).

Clearly, in "western" nations across the world, politicians are not rising into power based on any form of merit or competency. More and more politicians in the USA, UK and Australia are coming into power despite their clearly displayed inadequacy, in spite of their arrested development, they are wielding power and being voted in by a jaded and disengaged public.

I've got no clear answer for you, only more questions.

Increasing broader community participation in politics is one, albeit unlikely solution (although the recent success of socialists in the US mid-terms show it can be done).

Another is to completely change the nature of our Democracy so that it functions as advertised but you first need to remove those in power with vested interests in the status quo.

So, this was a book review. Fantastic, insightful and frightening. Well worth a read.

Book Review: Frankenstein or The modern Prometheus (uncensored 1818 edition)

Posted by Craige McWhirter on

by Mary Wollstonecraft Shelley

Frankenstein or The modern Prometheus: The 1818 Text

This is a book I've had a steadfast disinterest in my entire life. Horror, old horror and a subject that has been covered ad nauseum in pop culture - it simply held no interest.

That was until I heard Radio National's The Science Show's special Frankenstein - It’s Alive!!! on the 1818 edition being re-released for the 200th anniversary of it's publication. That program shifted this book from "pulp fiction" to "must read" in my mind. This episode of The Science Show is worth a listen on it's own and is one of my favourite pod casts.

Yes, the first version is in 1818, and there were several more of that first edition, hardly altered at all. And then there's the edition in 1831 which she'd really seriously revised, and she'd really changed the impact of it.

... And really the changes that were made there were because some critics and some readers obviously were rather shocked by the science, the interest in the life issue. They thought the whole thing was dynamite really, they thought the public shouldn't really be given this plot and they shouldn't really be encouraged to think about it, so that each version of it is really a censored version of it after that. The novel gets rather more like the stage plays. She realised that that was the way critics anyway and the respectable classes wanted the plot to be interpreted.

This piqued my interest in the novel for the first time.

One of Mary's personal anxieties that Fiona mentions had been the loss of her first-born child, born prematurely and living for only a few days. She wrote in her journal:

Dreamt that my little baby came to life again, that it had only been cold and that we rubbed it before the fire and it lived.

and this added a personal connection for me between the author and the material. One of my favourite authors, Philip K. Dick described himself as a philosopher who used novels to explore his philosophies. I'd come to view this book in a similar light.

Thus armed, I convinced the local library of the importance of having this particular edition in their collection and waited patiently for it to arrive.

This edition proved to be a rewarding and surprising read. The antiquated story telling devices take a little getting used to, along with the spelling of many words and words that have dropped from modern usage in the 200 years since it was first published.

This original edition, as the podcast expounded upon, is nothing like what we've become accustomed to in modern pop culture but is a much more textured, thought provoking read. It may have been horror to an audience in 1818 but for me, the author is clearly exploring interesting philosophical question and that's where my attention was firmly fixed.

I highly recommend reading this original edition of this work. It's a surprisingly short, fast paced and thought provoking read.

I was mistaken to have avoided it for so long.

Configuring Solarized Colours in Termonad

Posted by Craige McWhirter on

I'm currently using Termonad as my terminal of choice. What is Termonad?

Termonad is a terminal emulator configurable in Haskell. It is extremely customizable and provides hooks to modify the default behavior. It can be thought of as the "XMonad" of terminal emulators.

As a long time Xmonad user, this is a rather appealing description as well as a fairly lofty and worthy goal. It's also a niche not currently filled, one that I'm pretty happy to see being filled. By default, Termonad looks like this.

Default Termonad palette

Which is pretty standard as far as terminal defaults go but a long way from the eye-soothing grace of the Solarized palette which I essentially won't work without.

The Solarized dark output looks like this (Haskell in vim):

Solarized (dark) Termonad palette

This is the function controlling the dark palette:

    solarizedDark1 :: Vec N8 (Colour Double)
    solarizedDark1 =
         sRGB24   0  43  54 -- base03, background
      :* sRGB24 220  50  47 -- red
      :* sRGB24 133 153   0 -- green
      :* sRGB24 181 137   0 -- yellow
      :* sRGB24  38 139 210 -- blue
      :* sRGB24 211  54 130 -- magenta
      :* sRGB24  42 161 152 -- cyan
      :* sRGB24 238 232 213 -- base2
      :* EmptyVec

    solarizedDark2 :: Vec N8 (Colour Double)
    solarizedDark2 =
         sRGB24   7  54  66 -- base02, background highlights
      :* sRGB24 203  75  22 -- orange
      :* sRGB24  88 110 117 -- base01, comments / secondary text
      :* sRGB24 131 148 150 -- base0, body text / default code / primary content
      :* sRGB24 147 161 161 -- base1, optional emphasised content
      :* sRGB24 108 113 196 -- violet
      :* sRGB24 101 123 131 -- base00
      :* sRGB24 253 246 227 -- base3
      :* EmptyVec

The Solarized light output looks like this (Haskell in vim):

Solarized (light) Termonad palette

This is the function controlling the light palette:

    solarizedLight1 :: Vec N8 (Colour Double)
    solarizedLight1 =
         sRGB24 238 232 213 -- base2, background highlights
      :* sRGB24 220  50  47 -- red
      :* sRGB24 133 153   0 -- green
      :* sRGB24 181 137   0 -- yellow
      :* sRGB24  38 139 210 -- blue
      :* sRGB24 211  54 130 -- magenta
      :* sRGB24  42 161 152 -- cyan
      :* sRGB24   7  54  66 -- base02
      :* EmptyVec

    solarizedLight2 :: Vec N8 (Colour Double)
    solarizedLight2 =
         sRGB24 253 246 227 -- base3, background
      :* sRGB24 203  75  22 -- orange
      :* sRGB24 147 161 161 -- base1, comments / secondary text
      :* sRGB24 101 123 131 -- base00, body text / default code / primary content
      :* sRGB24  88 110 117 -- base01, optional emphasised content
      :* sRGB24 108 113 196 -- violet
      :* sRGB24 131 148 150 -- base0
      :* sRGB24   0  43  54 -- base03
      :* EmptyVec

You can see how I've applied these options in this commit.

Update: My complete example is now included in the termonad examples.

Finding Bugs in an Aercus WS3083

Posted by Craige McWhirter on

Aercus WS3083

While we're not drought declared, there's been precious little rain over the last 6 months or so, which made it hard to work out if I really had a problem or not.

The rainfall had been light and infrequent while each time the weather station recorded 0mm rainfall, which didn't seem unreasonable but was dubious nonetheless.

All the sensors appeared to be working OK. I re-seated, reconnected and reset everything to make sure that the system was connected and working fine but with no change in the result.

I eventually was driven to digging out the manual gauge which recorded 18mm the next time it rained while the weather station recorded 0mm.

No choice this time but to get up the ladder and disassemble the rain gauge.

I removed the cover and everything passed an initial eye balling.

Aercus WS3083 rain gauge

The Aercus WS3083 uses a lever arm to measure and discard collected rain. Being a simple primate, I attempted toggled it with my index finger, instantly noted it wasn't moving freely before a massive grasshopper|cricket flew out from under the arm and into my face. It had made a nice home under the arm, preventing it from dipping down and counting rain.

Unsurprisingly it moved freely after that and has been accurately measuring rain since.

Postscript: I think the chickens are still upset at me for failing to catch the grasshopper|cricket and feed it to them.

Querying Installed Package Versions Across An Openstack Cloud

Posted by Craige McWhirter on

AKA: The Joy of juju run

Package upgrades across an OpenStack cloud do not always happen at the same time. In most cases they may happen within an hour or so across your cloud but for a variety reasons, some upgrades may be applied inconsistently, delayed or blocked on some servers.

As these packages may be rolling out a much needed patch or perhaps carrying a bug, you may wish to know which services are impacted in fairly short order.

If your OpenStack cloud is running Ubuntu and managed by Juju and MAAS, here's where juju run can come to the rescue.

For example, perhaps there's an update to the Corosync library libcpg4 and you wish to know which of your HA clusters have what version installed.

From your Juju controller, create a list of servers managed by Juju:

Juju 1.x:

$ juju stat --format tabular > jsft.out

Now you could fashion a query like this, utilising juju run:

$ for i in $(egrep -o '[a-z]+-hacluster/[0-9]+' jsft.out | cut -d/ -f1 | sort -u);
do juju run --timeout 30s --service $i "dpkg-query -W -f='\${Version}' libcpg4" | \
python -c 'import yaml,sys;print("\n".join(["{} == {}".format(y["Stdout"], y["UnitId"]) for y in yaml.safe_load(sys.stdin)]))';
done

The output returned will look something like this:

2.3.3-1ubuntu4 == ceilometer-hacluster/1
2.3.3-1ubuntu4 == ceilometer-hacluster/0
2.3.3-1ubuntu4 == ceilometer-hacluster/2
2.3.3-1ubuntu4 == cinder-hacluster/0
2.3.3-1ubuntu4 == cinder-hacluster/1
2.3.3-1ubuntu4 == cinder-hacluster/2
2.3.3-1ubuntu4 == glance-hacluster/3
2.3.3-1ubuntu4 == glance-hacluster/4
2.3.3-1ubuntu4 == glance-hacluster/5
2.3.3-1ubuntu4 == keystone-hacluster/1
2.3.3-1ubuntu4 == keystone-hacluster/0
2.3.3-1ubuntu4 == keystone-hacluster/2
2.3.3-1ubuntu4 == mysql-hacluster/1
2.3.3-1ubuntu4 == mysql-hacluster/2
2.3.3-1ubuntu4 == mysql-hacluster/0
2.3.3-1ubuntu4 == ncc-hacluster/1
2.3.3-1ubuntu4 == ncc-hacluster/0
2.3.3-1ubuntu4 == ncc-hacluster/2
2.3.3-1ubuntu4 == neutron-hacluster/2
2.3.3-1ubuntu4 == neutron-hacluster/1
2.3.3-1ubuntu4 == neutron-hacluster/0
2.3.3-1ubuntu4 == osd-hacluster/0
2.3.3-1ubuntu4 == osd-hacluster/1
2.3.3-1ubuntu4 == osd-hacluster/2
2.3.3-1ubuntu4 == swift-hacluster/1
2.3.3-1ubuntu4 == swift-hacluster/0
2.3.3-1ubuntu4 == swift-hacluster/2

Juju 2.x:

$ juju status > jsft.out

Now you could fashion a query like this:

$ for i in $(egrep -o 'hacluster-[a-z]+/[0-9]+' jsft.out | cut -d/ -f1 |sort -u);
do juju run --timeout 30s --application $i "dpkg-query -W -f='\${Version}' libcpg4" | \
python -c 'import yaml,sys;print("\n".join(["{} == {}".format(y["Stdout"], y["UnitId"]) for y in yaml.safe_load(sys.stdin)]))';
done

The output returned will look something like this:

2.3.5-3ubuntu2 == hacluster-ceilometer/1
2.3.5-3ubuntu2 == hacluster-ceilometer/0
2.3.5-3ubuntu2 == hacluster-ceilometer/2
2.3.5-3ubuntu2 == hacluster-cinder/1
2.3.5-3ubuntu2 == hacluster-cinder/0
2.3.5-3ubuntu2 == hacluster-cinder/2
2.3.5-3ubuntu2 == hacluster-glance/0
2.3.5-3ubuntu2 == hacluster-glance/1
2.3.5-3ubuntu2 == hacluster-glance/2
2.3.5-3ubuntu2 == hacluster-heat/0
2.3.5-3ubuntu2 == hacluster-heat/1
2.3.5-3ubuntu2 == hacluster-heat/2
2.3.5-3ubuntu2 == hacluster-horizon/0
2.3.5-3ubuntu2 == hacluster-horizon/1
2.3.5-3ubuntu2 == hacluster-horizon/2
2.3.5-3ubuntu2 == hacluster-keystone/0
2.3.5-3ubuntu2 == hacluster-keystone/1
2.3.5-3ubuntu2 == hacluster-keystone/2
2.3.5-3ubuntu2 == hacluster-mysql/0
2.3.5-3ubuntu2 == hacluster-mysql/1
2.3.5-3ubuntu2 == hacluster-mysql/2
2.3.5-3ubuntu2 == hacluster-neutron/0
2.3.5-3ubuntu2 == hacluster-neutron/2
2.3.5-3ubuntu2 == hacluster-neutron/1
2.3.5-3ubuntu2 == hacluster-nova/1
2.3.5-3ubuntu2 == hacluster-nova/2
2.3.5-3ubuntu2 == hacluster-nova/0

You can of course substitute libcpg4 in the above query for any package that you need to check.

By far and away my most favourite feature of Juju at present, juju run reminds me of knife ssh, which is unsurprisingly one of my favourite features of Chef.

Resolving a Partitioned RabbitMQ Cluster with JuJu

Posted by Craige McWhirter on

On occasion, a RabbitMQ cluster may partition itself. In a OpenStack environment this can often first present itself as nova-compute services stopping with errors such as these:

ERROR nova.openstack.common.periodic_task [-] Error during ComputeManager._sync_power_states: Timed out waiting for a reply to message ID 8fc8ea15c5d445f983fba98664b53d0c
...
TRACE nova.openstack.common.periodic_task self._raise_timeout_exception(msg_id)
TRACE nova.openstack.common.periodic_task File "/usr/lib/python2.7/dist-packages/oslo/messaging/_drivers/amqpdriver.py", line 218, in _raise_timeout_exception
TRACE nova.openstack.common.periodic_task 'Timed out waiting for a reply to message ID %s' % msg_id)
TRACE nova.openstack.common.periodic_task MessagingTimeout: Timed out waiting for a reply to message ID 8fc8ea15c5d445f983fba98664b53d0c

Merely restarting the stopped nova-compute services will not resolve this issue.

You may also find that querying the rabbitmq service may either not return or take an awful long time to return:

$ sudo rabbitmqctl -p openstack list_queues name messages consumers status

...and in an environment managed by juju, you could also see JuJu trying to correct the RabbitMQ but failing:

$ juju stat --format tabular | grep rabbit
rabbitmq-server                       false local:trusty/rabbitmq-server-128
rabbitmq-server/0           idle   1.25.13.1 0/lxc/12 5672/tcp 192.168.7.148
rabbitmq-server/1   error   idle   1.25.13.1 1/lxc/8  5672/tcp 192.168.7.163   hook failed: "config-changed"
rabbitmq-server/2   error   idle   1.25.13.1 2/lxc/10 5672/tcp 192.168.7.174   hook failed: "config-changed"

You should now run rabbitmqctl cluster_status on each of your rabbit instances and review the output. If the cluster is partitioned, you will see something like the below:

ubuntu@my_juju_lxc:~$ sudo rabbitmqctl cluster_status
Cluster status of node 'rabbit@192-168-7-148' ...
[{nodes,[{disc,['rabbit@192-168-7-148','rabbit@192-168-7-163',
                'rabbit@192-168-7-174']}]},
 {running_nodes,['rabbit@192-168-7-174','rabbit@192-168-7-148']},
 {partitions,[{'rabbit@192-168-7-174',['rabbit@192-168-7-163']},
               {'rabbit@192-168-7-148',['rabbit@192-168-7-163']}]}]
...done.

You can clearly see from the above that there are two partitions for RabbitMQ. We need to now identify which of these is considered the leader:

maas-my_cloud:~$ juju run --service rabbitmq-server "is-leader"
- MachineId: 0/lxc/12
  Stderr: |
  Stdout: |
    True
  UnitId: rabbitmq-server/0
- MachineId: 1/lxc/8
  Stderr: |
  Stdout: |
    False
  UnitId: rabbitmq-server/1
- MachineId: 2/lxc/10
  Stderr: |
  Stdout: |
    False
  UnitId: rabbitmq-server/2

As you see above, in this example machine 0/lxc/12 is the leader, via it's status of "True". Now we need to hit the other two servers and shut down RabbitMQ:

# service rabbitmq-server stop

Once both services have completed shutting down, we can resolve the partitioning by running:

$ juju resolved -r rabbitmq-server/<whichever is leader>

Substituting <whichever is leader> for the machine ID identified earlier.

Once that has completed, you can start the previously stopped services with the below on each host:

# service rabbitmq-server start

and verify the result with:

$ sudo rabbitmqctl cluster_status
Cluster status of node 'rabbit@192-168-7-148' ...
[{nodes,[{disc,['rabbit@192-168-7-148','rabbit@192-168-7-163',
                'rabbit@192-168-7-174']}]},
 {running_nodes,['rabbit@192-168-7-163','rabbit@192-168-7-174',
                 'rabbit@192-168-7-148']},
 {partitions,[]}]
...done.

No partitions \o/

The JuJu errors for RabbitMQ should clear within a few minutes:

$ juju stat --format tabular | grep rabbit
rabbitmq-server                       false local:trusty/rabbitmq-server-128
rabbitmq-server/0             idle   1.25.13.1 0/lxc/12 5672/tcp 19 2.168.1.148
rabbitmq-server/1   unknown   idle   1.25.13.1 1/lxc/8  5672/tcp 19 2.168.1.163
rabbitmq-server/2   unknown   idle   1.25.13.1 2/lxc/10 5672/tcp 192.168.1.174

You should also find the nova-compute instances starting up fine.

First Look at Snaps

Posted by Craige McWhirter on

I've belatedly come to have a close up look at both Ubuntu Core (Snappy), Snaps and the Snappy package manager.

The first pass was to rebuild my rack of Raspberry Pi's from Debian armhf to Ubuntu Core for the Raspberry Pi.

Rack'o'Pi's)

This proved to be the most graceful install I've ever had on any hardware, ever. No hyperbole: boot, authenticate, done. I repeated this for all six Pi's in such a short time frame that I was concerned I'd done something wrong. Your SSH keys are already installed, you can log in immediately and just get on with it.

Which is where snaps come into play.

Back on my laptop, I followed the tutorial Create Your First Snap which uses GNU Hello as an example snap build and finishes with a push to the snap store at snapcraft.io.

I then created a Launchpad Repo, related a snap package, told it to build for armhf and amd64 and before long, I could install this snap on both my laptop and the Pi's.

Overall this was a pretty impressive and graceful process.

Japanese House in Python for Minecraft

Posted by Craige McWhirter on

I have kids that I'm teaching to hack. They started of on Scratch (which is excellent) and are ready to move up to Python. They also happen to be mad Minecraft fans, so now they're making their way through Adventures in Minecraft.

As I used Scratch when they were, I'm also hacking in Python & Minecraft as they are. It helps if I hit the bumps and hurdles before they do, as well as have a sound handle on the problems they're solving.

Now I've branched out from the tutorial and I'm just having fun with it and leaving behind code the kids can use, hack whatever. This code is in my minecraft-tools repo (for want of a better name). It's just a collection of random tools I've written for Minecraft aren't quite up to being their own thing. I expect this will mostly be a collection of python programs to construct things inside Minecraft via CanaryMod and CanaryRaspberryJuicePlugin.

The first bit of code to be shaken out of the tree is japanese_house.py which produces a Minecraft interpretation of a classic Japanese house. Presently it only produces the single configuration that is little more than an empty shell.

Japanese House (day) Japanese House
(night)

I intend to add an interior fit out plus a whole bunch of optional configurations you can set at run time but for now it is what it is, as I'm going to move onto writing geodesic domes and transport | teleport rings (as per the Expanse, which will lead to eventually coding a TARDIS, that will you know, be actually bigger on the inside ;-)