<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://labwiki.maceys.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Lynn</id>
	<title>My Homelab - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://labwiki.maceys.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Lynn"/>
	<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Special:Contributions/Lynn"/>
	<updated>2026-10-21T04:09:24Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1585</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1585"/>
		<updated>2026-09-04T01:15:51Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools that I use, or have used=&lt;br /&gt;
In my opinion, it makes no sense to run a hypervisor tool like VirtualBox or VMWare Workstations since it makes more sense to run docker natively rather than going to the complexity of a layer of abstraction. I also think that attempting to try to do this on a desktop is a little crazy as well. At the same time, if it was you have to test with; if it works, it works. These are listed roughly in order of use.&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes, it can be done but I personally think that it is crazy.&lt;br /&gt;
&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It passed through EMC, Dell and then to Broadcomm. Broadcomm eliminated the perpetual licensing, and the cost of ownership went way up. &lt;br /&gt;
&lt;br /&gt;
It was possible to download the product and get a limited use key. After yet another acquisition by venture capital firm KKR, the  VMWare Workstation Pro and VMWare Player shifted to a free for personal use status, but these ae really intended for an individual developer&#039;s desktop rather than a server implementation.&lt;br /&gt;
&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1584</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1584"/>
		<updated>2026-09-04T01:14:32Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
The software configuration has been a longer journey. Like a lot of people I started with a fairly simple sincle server implementation built around a self-crafted docker-compose.yml file that had, primarily, the *arr servers. This was front-ended with a Caddy configuration as the reverse proxy. It worked, but was somewhat difficult to manage. As I recall, I got hung up on configuring Authelia.&lt;br /&gt;
&lt;br /&gt;
I discovered SmarthomeBeginner which is now [https://www.simplehomelab.com/ SimpleHomeLab]. Anand has put together a large number of guides that largely map his journey. The guides are actually very good, but have to be taken independently. They are very good references but they are a moving target. Later guides tend to replace earlier ones and he has changed methods several times. There are a number of implementation strategies that have not really changed and they are a good reference for how things actually work. In short, this a another solution that requires a lot of localcustomization.&lt;br /&gt;
He later wrote a very comprehensive tool to implement a complete home server called Deployrr. See below.&lt;br /&gt;
&lt;br /&gt;
The next solution that I tried was [https://yams.media/install/steps/ YAMS], short for Yet Another Media Server. This is still running and serving the *arr collection from Chico1. It actually works pretty well, but needed Pi-Hole and Traefik as a separate front end to be truly useful. As a media server it does address setting up a VPN via Gluetun and making the *arrs available locally. &lt;br /&gt;
&lt;br /&gt;
Deployrr, originally Installarr, has been the most comprehensive too that I have seen so far. It is a licensed product that starts with a bare metal server and builds out the prerequisite tool set. Importantly, it totally configures the traefik reverse proxy tool. Built into deployrr are about 140 preconfigured applications. Extending that set can be easily accomplished; just install the application and tell Deployrr where it is. This feature makes up for the fact that the installed base of applications has not grown much in 2026. It is still an excellent choice however.&lt;br /&gt;
&lt;br /&gt;
Return to: &#039;&#039;&#039;[[Main Page]]&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1583</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1583"/>
		<updated>2026-09-04T00:41:08Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
The software configuration has been a longer journey. Like a lot of people I started with a fairly simple sincle server implementation built around a self-crafted docker-compose.yml file that had, primarily, the *arr servers. This was front-ended with a Caddy configuration as the reverse proxy. It worked, but was somewhat difficult to manage. As I recall, I got hung up on configuring Authelia.&lt;br /&gt;
&lt;br /&gt;
I discovered Smarthome&lt;br /&gt;
&lt;br /&gt;
The next solution that I tried was [https://yams.media/install/steps/ YAMS], short for Yet Another Media Server. This is still running and serving the *arr collection from Chico1. It actually works pretty well, but needed Pi-Hole and Traefik as a separate front end to be truely useful. &lt;br /&gt;
&lt;br /&gt;
Return to: &#039;&#039;&#039;[[Main Page]]&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1582</id>
		<title>The Network and Home Servers</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1582"/>
		<updated>2026-09-04T00:31:27Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* Qnap */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Key Servers=&lt;br /&gt;
==Cube==&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.&lt;br /&gt;
&lt;br /&gt;
====TrueNAS Qwerks====&lt;br /&gt;
Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&#039;t really recommended that they be modified, but this isn&#039;t a production shop and some things are easier a non-standard way. &lt;br /&gt;
&lt;br /&gt;
Create &lt;br /&gt;
&lt;br /&gt;
The following script is setup to run during the boot after the init stage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One that took some time to figure out was getting a storage pool set up on the USB array. It has 4 disks and I use it for things that I don&#039;t need too quickly. The interface is reliable but slow. TrueNAS doesn&#039;t recommend doing this and the GUI in recent releases is broken when you try to create a pool using those drives. It can be done through the command line though. This is filed under, I hope that I rarely, if ever, need to do this.&lt;br /&gt;
&lt;br /&gt;
The first step is to get and store the drive identification from the system:&lt;br /&gt;
&lt;br /&gt;
Do this as root.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir /mnt/coldstorage&lt;br /&gt;
chmod 777 /mnt/coldstorage&lt;br /&gt;
&lt;br /&gt;
ls /dev/disk/by-id |grep usb&amp;gt;drives&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This will create a mount location and provide a list of the USB drives that will be needed to create the pool. Edit the drives file that was created in the previous step and insert &amp;quot;/dev/disk/&amp;quot; at the beginning of each one. Then use the join command to put them on one line. Then put the following at the beginning of the single line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to get something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12 &amp;lt;drive1&amp;gt; &amp;lt;drive2&amp;gt; &amp;lt;drive3&amp;gt; &amp;lt;drive4&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Save and execute:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sh drives&lt;br /&gt;
zpool export coldstorage&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The pool is created but needs to be imported through the GUI to be available. Go to the Storage Dashboard and import it.&lt;br /&gt;
&lt;br /&gt;
{{note | this assumes that there there is only a single USB array if there is more than one, it will be necessary to sort out which drives go where.}}&lt;br /&gt;
&lt;br /&gt;
==PVE==&lt;br /&gt;
===Proxmox===&lt;br /&gt;
==Qnap (macey)==&lt;br /&gt;
&lt;br /&gt;
Return to: &#039;&#039;&#039;[[Main Page]]&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1581</id>
		<title>The Network and Home Servers</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1581"/>
		<updated>2026-09-04T00:30:18Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Key Servers=&lt;br /&gt;
==Cube==&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.&lt;br /&gt;
&lt;br /&gt;
====TrueNAS Qwerks====&lt;br /&gt;
Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&#039;t really recommended that they be modified, but this isn&#039;t a production shop and some things are easier a non-standard way. &lt;br /&gt;
&lt;br /&gt;
Create &lt;br /&gt;
&lt;br /&gt;
The following script is setup to run during the boot after the init stage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One that took some time to figure out was getting a storage pool set up on the USB array. It has 4 disks and I use it for things that I don&#039;t need too quickly. The interface is reliable but slow. TrueNAS doesn&#039;t recommend doing this and the GUI in recent releases is broken when you try to create a pool using those drives. It can be done through the command line though. This is filed under, I hope that I rarely, if ever, need to do this.&lt;br /&gt;
&lt;br /&gt;
The first step is to get and store the drive identification from the system:&lt;br /&gt;
&lt;br /&gt;
Do this as root.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir /mnt/coldstorage&lt;br /&gt;
chmod 777 /mnt/coldstorage&lt;br /&gt;
&lt;br /&gt;
ls /dev/disk/by-id |grep usb&amp;gt;drives&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This will create a mount location and provide a list of the USB drives that will be needed to create the pool. Edit the drives file that was created in the previous step and insert &amp;quot;/dev/disk/&amp;quot; at the beginning of each one. Then use the join command to put them on one line. Then put the following at the beginning of the single line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to get something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12 &amp;lt;drive1&amp;gt; &amp;lt;drive2&amp;gt; &amp;lt;drive3&amp;gt; &amp;lt;drive4&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Save and execute:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sh drives&lt;br /&gt;
zpool export coldstorage&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The pool is created but needs to be imported through the GUI to be available. Go to the Storage Dashboard and import it.&lt;br /&gt;
&lt;br /&gt;
{{note | this assumes that there there is only a single USB array if there is more than one, it will be necessary to sort out which drives go where.}}&lt;br /&gt;
&lt;br /&gt;
==PVE==&lt;br /&gt;
===Proxmox===&lt;br /&gt;
==Qnap==&lt;br /&gt;
&lt;br /&gt;
Return to: &#039;&#039;&#039;[[Main Page]]&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1580</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1580"/>
		<updated>2026-09-03T23:38:54Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
Return to: &#039;&#039;&#039;[[Main Page]]&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1579</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1579"/>
		<updated>2026-09-03T23:38:28Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
Return to: [[Main Page]]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1578</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1578"/>
		<updated>2026-09-03T23:37:50Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
Return to: [[My Homelab]]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1577</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1577"/>
		<updated>2026-09-03T23:37:32Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
Return to: [[My Home Lab]]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1576</id>
		<title>How did I get here?</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=How_did_I_get_here%3F&amp;diff=1576"/>
		<updated>2026-09-03T23:36:55Z</updated>

		<summary type="html">&lt;p&gt;Lynn: Created page with &amp;quot;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&amp;#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1575</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1575"/>
		<updated>2026-09-03T23:36:47Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
=Introduction=&lt;br /&gt;
This wiki covers my journey toward the perfect Homelab/Home server for me. This likely is not the perfect one for you. &lt;br /&gt;
&lt;br /&gt;
I am a retired IP professional who spent 45 years in the trenches doing much this same work and got paid for it. Now I do it for fun and, face it, it keeps off of the streets and out of jail. &lt;br /&gt;
&lt;br /&gt;
[[How did I get here?]]&lt;br /&gt;
&lt;br /&gt;
[[The Network and Home Servers]]&lt;br /&gt;
&lt;br /&gt;
I have been through a number of solution packages getting to where I am, and I am largely still looking. Hopefully I will be able to declare success in the relatively near future...&lt;br /&gt;
&lt;br /&gt;
Like many of you I started out with discrete tools as I was learning how to use Docker. I am not sure how I would get along without this tool. Below I will list what I feel are the strengths and weaknesses of the configurations that I have tried. To a large degree, if you starting out building a home lab, you need to have the ability to manage the configuration that you pt together because generally none of them are a perfect solution. One of the common problems since the developers are likely to be home hobbyists like ourselves, The projects tend to stall out after a while. The developer moves on or experiences people being demanding, ungrateful and sometimes just plain nasty. &lt;br /&gt;
&lt;br /&gt;
I have been willing, and happy, to pay them subscription fees to fund their efforts. These are usually modest, typically equivalent to two or three lattes a month. Getting traction on their part is often difficult and reaching critical mass generally doesn&#039;t happen. I recognize that most of these people have real jobs and developing the code is just a side gig that helps to pay the electric bill and buy some hardware. So, with that, let me tell you how I got to here.&lt;br /&gt;
&lt;br /&gt;
[[The Homelab Stack History]]&lt;br /&gt;
&lt;br /&gt;
The stack implementation progression has been SimpleHomeLab, Funky Penguin, YAMS and then Deployrr. I am now working with The TrueNAS-Compose suite. I have tried to give a reasoned top-level assessment of the good and bad aspects of the tool on the assessment page.&lt;br /&gt;
&lt;br /&gt;
[[The assessments are here]]&lt;br /&gt;
&lt;br /&gt;
For more of a deep dive deep dive into them just head over here:&lt;br /&gt;
&lt;br /&gt;
[[Technical details are here]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1574</id>
		<title>The Network and Home Servers</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1574"/>
		<updated>2026-08-24T21:09:52Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* TrueNAS Qwerks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Key Servers=&lt;br /&gt;
==Cube==&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.&lt;br /&gt;
&lt;br /&gt;
====TrueNAS Qwerks====&lt;br /&gt;
Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&#039;t really recommended that they be modified, but this isn&#039;t a production shop and some things are easier a non-standard way. &lt;br /&gt;
&lt;br /&gt;
Create &lt;br /&gt;
&lt;br /&gt;
The following script is setup to run during the boot after the init stage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One that took some time to figure out was getting a storage pool set up on the USB array. It has 4 disks and I use it for things that I don&#039;t need too quickly. The interface is reliable but slow. TrueNAS doesn&#039;t recommend doing this and the GUI in recent releases is broken when you try to create a pool using those drives. It can be done through the command line though. This is filed under, I hope that I rarely, if ever, need to do this.&lt;br /&gt;
&lt;br /&gt;
The first step is to get and store the drive identification from the system:&lt;br /&gt;
&lt;br /&gt;
Do this as root.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir /mnt/coldstorage&lt;br /&gt;
chmod 777 /mnt/coldstorage&lt;br /&gt;
&lt;br /&gt;
ls /dev/disk/by-id |grep usb&amp;gt;drives&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This will create a mount location and provide a list of the USB drives that will be needed to create the pool. Edit the drives file that was created in the previous step and insert &amp;quot;/dev/disk/&amp;quot; at the beginning of each one. Then use the join command to put them on one line. Then put the following at the beginning of the single line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to get something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12 &amp;lt;drive1&amp;gt; &amp;lt;drive2&amp;gt; &amp;lt;drive3&amp;gt; &amp;lt;drive4&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Save and execute:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sh drives&lt;br /&gt;
zpool export coldstorage&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The pool is created but needs to be imported through the GUI to be available. Go to the Storage Dashboard and import it.&lt;br /&gt;
&lt;br /&gt;
{{note | this assumes that there there is only a single USB array if there is more than one, it will be necessary to sort out which drives go where.}}&lt;br /&gt;
&lt;br /&gt;
==PVE==&lt;br /&gt;
===Proxmox===&lt;br /&gt;
==Qnap==&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1573</id>
		<title>The Network and Home Servers</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1573"/>
		<updated>2026-08-24T21:09:24Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* TrueNAS Qwerks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Key Servers=&lt;br /&gt;
==Cube==&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.&lt;br /&gt;
&lt;br /&gt;
====TrueNAS Qwerks====&lt;br /&gt;
Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&#039;t really recommended that they be modified, but this isn&#039;t a production shop and some things are easier a non-standard way. &lt;br /&gt;
&lt;br /&gt;
Create &lt;br /&gt;
&lt;br /&gt;
The following script is setup to run during the boot after the init stage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One that took some time to figure out was getting a storage pool set up on the USB array. It has 4 disks and I use it for things that I don&#039;t need too quickly. The interface is reliable but slow. TrueNAS doesn&#039;t recommend doing this and the GUI in recent releases is broken when you try to create a pool using those drives. It can be done through the command line though. This is filed under, I hope that I rarely, if ever, need to do this.&lt;br /&gt;
&lt;br /&gt;
The first step is to get and store the drive identification from the system:&lt;br /&gt;
&lt;br /&gt;
Do this as root.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir /mnt/coldstorage&lt;br /&gt;
chmod 777 /mnt/coldstorage&lt;br /&gt;
&lt;br /&gt;
ls /dev/disk/by-id |grep usb&amp;gt;drives&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This will create a mount location and provide a list of the USB drives that will be needed to create the pool. Edit the drives file that was created in the previous step and insert &amp;quot;/dev/disk/&amp;quot; at the beginning of each one. Then use the join command to put them on one line. Then put the following at the beginning of the single line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to get something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12 &amp;lt;drive1&amp;gt; &amp;lt;drive2&amp;gt; &amp;lt;drive3&amp;gt; &amp;lt;drive4&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Save and execute:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sh drives&lt;br /&gt;
zpool export coldstorage&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The pool is created but needs to be imported through the GUI to be available. Go to the Storage Dashboard and import it.&lt;br /&gt;
&lt;br /&gt;
{{note | this assumes that there there is only a single USB array if there is more than one, it will be necessary to sort out which drives go where.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The&lt;br /&gt;
&lt;br /&gt;
==PVE==&lt;br /&gt;
===Proxmox===&lt;br /&gt;
==Qnap==&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1572</id>
		<title>The Network and Home Servers</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1572"/>
		<updated>2026-08-24T21:08:47Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* TrueNAS Querks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Key Servers=&lt;br /&gt;
==Cube==&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.&lt;br /&gt;
&lt;br /&gt;
====TrueNAS Qwerks====&lt;br /&gt;
Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&#039;t really recommended that they be modified, but this isn&#039;t a production shop and some things are easier a non-standard way. &lt;br /&gt;
&lt;br /&gt;
Create &lt;br /&gt;
&lt;br /&gt;
The following script is setup to run during the boot after the init stage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One that took some time to figure out was getting a storage pool set up on the USB array. It has 4 disks and I use it for things that I don&#039;t need too quickly. The interface is reliable but slow. TrueNAS doesn&#039;t recommend doing this and the GUI in recent releases is broken when you try to create a pool using those drives. It can be done through the command line though. This is filed under, I hope that I rarely, if ever, need to do this.&lt;br /&gt;
&lt;br /&gt;
The first step is to get and store the drive identification from the system:&lt;br /&gt;
&lt;br /&gt;
Do this as root.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir /mnt/coldstorage&lt;br /&gt;
chmod 777 /mnt/coldstorage&lt;br /&gt;
&lt;br /&gt;
ls /dev/disk/by-id |grep usb&amp;gt;drives&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This will create a mount location and provide a list of the USB drives that will be needed to create the pool. Edit the drives file that was created in the previous step and insert &amp;quot;/dev/disk/&amp;quot; at the beginning of each one. Then use the join command to put them on one line. Then put the following at the beginning of the single line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to get something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12 &amp;lt;drive1&amp;gt; &amp;lt;drive2&amp;gt; &amp;lt;drive3&amp;gt; &amp;lt;drive4&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Save and execute:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sh drives&lt;br /&gt;
zpool export coldstorage&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The pool is created but needs to be imported through the GUI to be available. Go to the Storage Dashboard and import it.&lt;br /&gt;
&lt;br /&gt;
{{note | this assumes that there there is only a single USB array if there is more than one, it will be necessary to sort out which drives go where.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The&lt;br /&gt;
&lt;br /&gt;
==PVE==&lt;br /&gt;
===Proxmox===&lt;br /&gt;
==Qnap==&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1571</id>
		<title>The Network and Home Servers</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Network_and_Home_Servers&amp;diff=1571"/>
		<updated>2026-08-24T21:05:46Z</updated>

		<summary type="html">&lt;p&gt;Lynn: Created page with &amp;quot;=Key Servers= ==Cube== ===TrueNAS=== TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.  ====TrueNAS Querks==== Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&amp;#039;t really recommended that they be modified, but this isn&amp;#039;t a production shop and some things are easier a non-standard way.   Create   The following...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Key Servers=&lt;br /&gt;
==Cube==&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
TrueNAS provides a rather comprehensive Network Attached Storage solution. I prefer it to Qnap.&lt;br /&gt;
&lt;br /&gt;
====TrueNAS Querks====&lt;br /&gt;
Any time the system is rebooted, a number of system directories and files are reverted to the originals as installed. These include /etc/fstab and /etc/sudoers.d. It isn&#039;t really recommended that they be modified, but this isn&#039;t a production shop and some things are easier a non-standard way. &lt;br /&gt;
&lt;br /&gt;
Create &lt;br /&gt;
&lt;br /&gt;
The following script is setup to run during the boot after the init stage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One that took some time to figure out was getting a storage pool set up on the USB array. It has 4 disks and I use it for things that I don&#039;t need too quickly. The interface is reliable but slow. TrueNAS doesn&#039;t recommend doing this and the GUI in recent releases is broken when you try to create a pool using those drives. It can be done through the command line though. This is filed under, I hope that I rarely, if ever, need to do this.&lt;br /&gt;
&lt;br /&gt;
The first step is to get and store the drive identification from the system:&lt;br /&gt;
&lt;br /&gt;
Do this as root.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir /mnt/coldstorage&lt;br /&gt;
chmod 777 /mnt/coldstorage&lt;br /&gt;
&lt;br /&gt;
ls /dev/disk/by-id |grep usb&amp;gt;drives&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This will create a mount location and provide a list of the USB drives that will be needed to create the pool. Edit the drives file that was created in the previous step and insert &amp;quot;/dev/disk/&amp;quot; at the beginning of each one. Then use the join command to put them on one line. Then put the following at the beginning of the single line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
zpool create -R /mnt coldstorage raidz -o failmode=continue -o ashift=12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Save and execute:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sh drives&lt;br /&gt;
zpool export coldstorage&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The pool is created but needs to be imported through the GUI to be available. Go to the Storage Dashboard and import it.&lt;br /&gt;
&lt;br /&gt;
{{note | this assumes that there there is only a single USB array if there is more than one, it will be necessary to sort out which drives go where.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The&lt;br /&gt;
==PVE==&lt;br /&gt;
===Proxmox===&lt;br /&gt;
==Qnap==&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1570</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1570"/>
		<updated>2026-08-21T21:57:09Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* The tools That I use, or have used */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
In my opinion, it makes no sense to run a hypervisor tool like VirtualBox or VMWare Workstations since it makes more sense to run docker natively rather than going to the complexity of a layer of abstraction. I also think that attempting to try to do this on a desktop is a little crazy as well. At the same time, if it was you have to test with; if it works, it works. These are listed roughly in order of use.&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes, it can be done but I personally think that it is crazy.&lt;br /&gt;
&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It passed through EMC, Dell and then to Broadcomm. Broadcomm eliminated the perpetual licensing, and the cost of ownership went way up. &lt;br /&gt;
&lt;br /&gt;
It was possible to download the product and get a limited use key. After yet another acquisition by venture capital firm KKR, the  VMWare Workstation Pro and VMWare Player shifted to a free for personal use status, but these ae really intended for an individual developer&#039;s desktop rather than a server implementation.&lt;br /&gt;
&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1569</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1569"/>
		<updated>2026-08-21T21:56:25Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* VMWare */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
In my opinion, it makes no sense to run a hypervisor tool like VirtualBox or VMWare Workstations since it makes more sense to run docker natively rather than going to the complexity of a layer of abstraction. I also think that attempting to try to do this on a desktop is a little crazy as well. At the same time, if it was you have to test with; if it works, it works.&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes, it can be done but I personally think that it is crazy.&lt;br /&gt;
&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It passed through EMC, Dell and then to Broadcomm. Broadcomm eliminated the perpetual licensing, and the cost of ownership went way up. &lt;br /&gt;
&lt;br /&gt;
It was possible to download the product and get a limited use key. After yet another acquisition by venture capital firm KKR, the  VMWare Workstation Pro and VMWare Player shifted to a free for personal use status, but these ae really intended for an individual developer&#039;s desktop rather than a server implementation.&lt;br /&gt;
&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1568</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1568"/>
		<updated>2026-08-21T21:53:57Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* Hypervisors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
In my opinion, it makes no sense to run a hypervisor tool like VirtualBox or VMWare Workstations since it makes more sense to run docker natively rather than going to the complexity of a layer of abstraction. I also think that attempting to try to do this on a desktop is a little crazy as well. At the same time, if it was you have to test with; if it works, it works.&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes, it can be done but I personally think that it is crazy.&lt;br /&gt;
&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It passed through EMC, Dell and then to Broadcomm. Broadcomm eliminated the perpetual licensing, and the cost of ownership went way up. &lt;br /&gt;
&lt;br /&gt;
It was possible to download the product and get a limited use key. After yet another acquisition by venture capital firm KKR, the  VMWare Workstation Pro and VMWare Player shifted to a free for personal use status, but these ae really intended for an individual developer&#039;s desktop rather than a server application.&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1567</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1567"/>
		<updated>2026-08-21T21:53:39Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* Hypervisors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
In my opinion, it makes no sense to run a hypervisor tool like VirtualBox or VMWare Workstations since it makes more sense to run docker natively rather than going to the complexity of a layer of abstraction. I also think that attempting to try to do this on a desktop is a little crazy as well. At the same time, if it was you have to test with; if it works, it works.&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes, it can be done but I personally think that it is crazy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It passed through EMC, Dell and then to Broadcomm. Broadcomm eliminated the perpetual licensing, and the cost of ownership went way up. &lt;br /&gt;
&lt;br /&gt;
It was possible to download the product and get a limited use key. After yet another acquisition by venture capital firm KKR, the  VMWare Workstation Pro and VMWare Player shifted to a free for personal use status, but these ae really intended for an individual developer&#039;s desktop rather than a server application.&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1566</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1566"/>
		<updated>2026-08-21T21:51:06Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* The tools That I use, or have used */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
In my opinion, it makes no sense to run a hypervisor tool like VirtualBox or VMWare Workstations since it makes more sense to run docker natively rather than going to the complexity of a layer of abstraction. I also think that attempting to try to do this on a desktop is a little crazy as well. At the same time, if it was you have to test with; if it works, it works.&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes it can be done but I personally think that it is crazy.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It passed through EMC, Dell and then to Broadcomm. Broadcomm eliminated the perpetual licensing, and the cost of ownership went way up. &lt;br /&gt;
&lt;br /&gt;
It was possible to download the product and get a limited use key. After yet another acquisition by venture capital firm KKR, the  VMWare Workstation Pro and VMWare Player shifted to a free for personal use status, but these ae really intended for an individual developer&#039;s desktop rather than a server application.&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1565</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1565"/>
		<updated>2026-08-21T21:36:13Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* Hypervisors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes it can be done but I personally think that it is crazy.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It was possible to download the product and get a limited use key. You can still get freeware copies of VMWare Workstation Pro and VMWare Player for free, but these ae really intended for an individual developer&#039;s desktop rather than a server application.&lt;br /&gt;
===VirtualBox===&lt;br /&gt;
This was the first virtualization product that I used. It runs on a number of architectures as an application. It was developed by Sun Computer Systems and moved to Oracle when Sun was acquired.&lt;br /&gt;
&lt;br /&gt;
It is a nice tool for testing on your desktop.&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1564</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1564"/>
		<updated>2026-08-21T21:30:47Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* Hyper-V */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes it can be done but I personally think that it is crazy.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
===VMWare===&lt;br /&gt;
Stay away, far, far away and it is still too close. They were the Gold Standard and about the only virtualization tool on the block. A decade or so, ago VMWare treated noncommercial users pretty well. It was possible to download the product and get a limited use key. You can still get freeware copies of VMWare Workstation Pro and VMWARE player&lt;br /&gt;
&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1563</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1563"/>
		<updated>2026-08-21T21:01:08Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* The tools That I use, or have used */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes it can be done but I personally think that it is crazy.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
==Foundations==&lt;br /&gt;
===Docker===&lt;br /&gt;
====Docker Compose====&lt;br /&gt;
====Dockerge====&lt;br /&gt;
====Dockhand====&lt;br /&gt;
===VPN===&lt;br /&gt;
====Private Internet Access====&lt;br /&gt;
====Nord====&lt;br /&gt;
==Docker Stacks==&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1562</id>
		<title>The assessments are here</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_assessments_are_here&amp;diff=1562"/>
		<updated>2026-08-21T02:00:27Z</updated>

		<summary type="html">&lt;p&gt;Lynn: Created page with &amp;quot;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days.   =The tools...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The software landscape is vast. It is a little like being in a candy store the size of a Walmart. With hypervisors like Proxmox and TrueNAS it is rediculously easy to sping up virtual machines or launch software with docker. What used to take hours installing an OS and configuring a physical resource only takes minutes. Installing software used to be its own hurdle. Getting it to work on you specific combination of hardware and OS could literally take days. &lt;br /&gt;
&lt;br /&gt;
=The tools That I use, or have used=&lt;br /&gt;
==Hypervisors==&lt;br /&gt;
For the most part these tools are bare metal installs. Let&#039;s not get into the crazy case where a hypervisor is installed on a hypervisor. Yes it can be done but I personally think that it is crazy.&lt;br /&gt;
===Proxmox===&lt;br /&gt;
===TrueNAS===&lt;br /&gt;
===Hyper-V===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=YAMS=&lt;br /&gt;
[https://gitlab.com/rogs/yams YAMS on Github]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1561</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1561"/>
		<updated>2026-08-21T01:43:11Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
=Introduction=&lt;br /&gt;
This wiki covers my journey toward the perfect Homelab/Home server for me. This likely is not the perfect one for you. &lt;br /&gt;
&lt;br /&gt;
I am a retired IP professional who spent 45 years in the trenches doing much this same work and got paid for it. Now I do it for fun and, face it, it keeps off of the streets and out of jail. &lt;br /&gt;
&lt;br /&gt;
[[How did I get here?]]&lt;br /&gt;
&lt;br /&gt;
I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
[[The Network and Home Servers]]&lt;br /&gt;
&lt;br /&gt;
I have been through a number of solution packages getting to where I am, and I am largely still looking. Hopefully I will be able to declare success in the relatively near future...&lt;br /&gt;
&lt;br /&gt;
Like many of you I started out with discrete tools as I was learning how to use Docker. I am not sure how I would get along without this tool. Below I will list what I feel are the strengths and weaknesses of the configurations that I have tried. To a large degree, if you starting out building a home lab, you need to have the ability to manage the configuration that you pt together because generally none of them are a perfect solution. One of the common problems since the developers are likely to be home hobbyists like ourselves, The projects tend to stall out after a while. The developer moves on or experiences people being demanding, ungrateful and sometimes just plain nasty. &lt;br /&gt;
&lt;br /&gt;
I have been willing, and happy, to pay them subscription fees to fund their efforts. These are usually modest, typically equivalent to two or three lattes a month. Getting traction on their part is often difficult and reaching critical mass generally doesn&#039;t happen. I recognize that most of these people have real jobs and developing the code is just a side gig that helps to pay the electric bill and buy some hardware. So, with that, let me tell you how I got to here.&lt;br /&gt;
&lt;br /&gt;
[[The Homelab Stack History]]&lt;br /&gt;
&lt;br /&gt;
The stack implementation progression has been SimpleHomeLab, Funky Penguin, YAMS and then Deployrr. I am now working with The TrueNAS-Compose suite. I have tried to give a reasoned top-level assessment of the good and bad aspects of the tool on the assessment page.&lt;br /&gt;
&lt;br /&gt;
[[The assessments are here]]&lt;br /&gt;
&lt;br /&gt;
For more of a deep dive deep dive into them just head over here:&lt;br /&gt;
&lt;br /&gt;
[[Technical details are here]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1560</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1560"/>
		<updated>2026-08-21T01:37:03Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
=Introduction=&lt;br /&gt;
This wiki covers my journey toward the perfect Homelab/Home server for me. This likely is not the perfect one for you. &lt;br /&gt;
&lt;br /&gt;
I am a retired IP professional who spent 45 years in the trenches doing much this same work and got paid for it. Now I do it for fun and, face it, it keeps off of the streets and out of jail. &lt;br /&gt;
&lt;br /&gt;
[[How did I get here?]]&lt;br /&gt;
&lt;br /&gt;
I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
[[The Network and Home Servers]]&lt;br /&gt;
&lt;br /&gt;
I have been through a number of solution packages getting to where I am, and I am largely still looking. Hopefully I will be able to declare success in the relatively near future...&lt;br /&gt;
&lt;br /&gt;
Like many of you I started out with discrete tools as I was learning how to use Docker. I am not sure how I would get along without this tool. Below I will list what I feel are the strengths and weaknesses of the configurations that I have tried. To a large degree, if you starting out building a home lab, you need to have the ability to manage the configuration that you pt together because generally none of them are a perfect solution. One of the common problems since the developers are likely to be home hobbyists like ourselves, The projects tend to stall out after a while. The developer moves on or experiences people being demanding, ungrateful and sometimes just plain nasty. &lt;br /&gt;
&lt;br /&gt;
I have been willing, and happy, to pay them subscription fees to fund their efforts. These are usually modest, typically equivalent to two or three lattes a month. Getting traction on their part is often difficult and reaching critical mass generally doesn&#039;t happen. I recognize that most of these people have real jobs and developing the code is just a side gig that helps to pay the electric bill and buy some hardware. So, with that, let me tell you how I got to here.&lt;br /&gt;
&lt;br /&gt;
[[The Homelab Stack History]]&lt;br /&gt;
&lt;br /&gt;
The stack implementation progression has been YAMS, Funky Penguin and Deployrr. I am now working with The TrueNAS-Compose suite. I have tried to give a reasoned top level assessment of the good and bad aspects of the tool on the assessment page.&lt;br /&gt;
&lt;br /&gt;
[[The assessments are here]]&lt;br /&gt;
&lt;br /&gt;
For more of a deep dive deep dive into them just head over here:&lt;br /&gt;
&lt;br /&gt;
[[Technical details are here]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Homelab_Stack_History&amp;diff=1559</id>
		<title>The Homelab Stack History</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Homelab_Stack_History&amp;diff=1559"/>
		<updated>2026-08-21T01:33:55Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=The early days=&lt;br /&gt;
Like most systems administrators, I was loath to throw anything away that wasn&#039;t broken. Time and time again, having that odd cable, odd peripheral or obscure tool saved me a lot of time and the companies money. &lt;br /&gt;
&lt;br /&gt;
Even before I retired, I couldn&#039;t keep it all. this eventually led to major runs to the e-waste facility. It is interesting that those places tend to come and go pretty frequently. From observation, it appears that the have an imbalance between intake and disposal.&lt;br /&gt;
&lt;br /&gt;
So, I confess that I am a data hoarder. I was a very early adopter of software on CDs. I actually had a subscription that brought me 10 to 20 disks/month. Many of them were not terrible useful at the time but the good ones in the bundle made up for it. I think that some of them are still in the back of a closet waiting for hardware and OS environments that only exist in the computer history museum.&lt;br /&gt;
&lt;br /&gt;
At that point in time I had a large collection of ripped music CDs, that are still at the house that we left to our daughter, books, a lot of files from various jobs and a ton of email. Video file came along later. I gathered stuff, and still do, for the sake of the collecting. Now sharing with friends and family is a side benefit but at the time it required burning CDs or DVDs. Streaming was not even feasible.&lt;br /&gt;
=Along Came High-Speed links=&lt;br /&gt;
It was suddenly possible to have just about anything accessible with a little looking and a small investment of time. The price of disk storage was coming down; I still remember being impressed when it crossed the $.25 per megabyte threshold. Clearly the resource had to be used. What took days to download with even a 56Kb modem could be fetched in minutes.&lt;br /&gt;
=Then Plex entered and the *arr stack to manage it=&lt;br /&gt;
I could now watch movies on my computer. At first, all it took was to put the files in the root folder and call it good. You all know the drill. Suddenly you have hundreds and the thousands of hours of content. I spun up a NAS and ran Plex from there&lt;br /&gt;
&lt;br /&gt;
The *arr collection added a means to organize the chaos. Initially, Radarr and Sonarr ran natively on a Linux system that had been a desktop at one time. Then I moved to a significant server and installed Proxmox. Then I added a large TrueNAS system that I am presently consolidating most everything on.&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=The_Homelab_Stack_History&amp;diff=1558</id>
		<title>The Homelab Stack History</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=The_Homelab_Stack_History&amp;diff=1558"/>
		<updated>2026-08-21T01:33:18Z</updated>

		<summary type="html">&lt;p&gt;Lynn: Created page with &amp;quot;=The early days= Like most systems administrators, I was loath to throw anything away that wasn&amp;#039;t broken. Time and time again, having that odd cable, odd peripheral or obscure tool saved me a lot of time and the companies money.   Even before I retired, I couldn&amp;#039;t keep it all. this eventually led to major runs to the e-waste facility. It is interesting that those places tend to come and go pretty frequently. From observation, it appears that the have an imbalance between...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=The early days=&lt;br /&gt;
Like most systems administrators, I was loath to throw anything away that wasn&#039;t broken. Time and time again, having that odd cable, odd peripheral or obscure tool saved me a lot of time and the companies money. &lt;br /&gt;
&lt;br /&gt;
Even before I retired, I couldn&#039;t keep it all. this eventually led to major runs to the e-waste facility. It is interesting that those places tend to come and go pretty frequently. From observation, it appears that the have an imbalance between intake and disposal.&lt;br /&gt;
&lt;br /&gt;
So, I confess that I am a data hoarder. I was a very early adopter of software on CDs. I actually had a subscription that brought me 10 to 20 disks/month. Many of them were not terrible useful at the time but the good ones in the bundle made up for it. I think that some of them are still in the back of a closet waiting for hardware and OS environments that only exist in the computer history museum.&lt;br /&gt;
&lt;br /&gt;
At that point in time I had a large collection of ripped music CDs, that are still at the house that we left to our daughter, books, a lot of files from various jobs and a ton of email. Video file came along later. I gathered stuff, and still do, for the sake of the collecting. Now sharing with friends and family is a side benefit but at the time it required burning CDs or DVDs. Streaming was not even feasible.&lt;br /&gt;
=Along Came High-Speed links=&lt;br /&gt;
It was suddenly possible to have just about anything accessible with a little looking and a small investment of time. The price of disk storage was coming down; I still remember being impressed when it crossed the $.25 per megabyte threshold. Clearly the resource had to be used. What took days to download with even a 56Kb modem could be fetched in minutes.&lt;br /&gt;
=Then Plex entered and the *arr stack to manage it=&lt;br /&gt;
I could now watch movies on my computer. At first, all it took was to put the files in the root folder and call it good. You all know the drill. Suddenly you have hundreds and the thousands of hours of content. I spun up a NAS and ran Plex from there&lt;br /&gt;
&lt;br /&gt;
The *arr collection added a means to organize the chaos. Initially, Radarr and Sonarr ran natively on a Linux system that had been a desktop at one time. Then I moved to a significant server and installed Proxmox. Then I added a large TrueNAS system that I am presently consolidating most everything on.&lt;br /&gt;
&lt;br /&gt;
The progression has been YAMS, Funky Penguin and Deployrr. I am now working with The TrueNAS-Compose suite.&lt;br /&gt;
&lt;br /&gt;
[[The assessments are here]]&lt;br /&gt;
&lt;br /&gt;
[[Technical details are here]]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1557</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1557"/>
		<updated>2026-08-21T00:54:14Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
=Introduction=&lt;br /&gt;
This wiki covers my journey toward the perfect Homelab/Home server for me. This likely is not the perfect one for you. &lt;br /&gt;
&lt;br /&gt;
I am a retired IP professional who spent 45 years in the trenches doing much this same work and got paid for it. Now I do it for fun and, face it, it keeps off of the streets and out of jail. &lt;br /&gt;
&lt;br /&gt;
[[How did I get here?]]&lt;br /&gt;
&lt;br /&gt;
I have been through a lot of hardware, software and solutions over the years. As of now, I am regretting scrapping some of the hardware that arrived over time. I don&#039;t feel bad about getting rid of the large 1U and 2U servers that I had at one time. They were not terribly fast, but very noisy and power hungry. After my wife and I downsized from a medium sized house to a 2 BR apartment, it made no sense to keep them. Yes, they were fun to tinker with, but they were also a sledgehammer to the fine task that I was attempting to accomplish.&lt;br /&gt;
&lt;br /&gt;
[[The Network and Home Servers]]&lt;br /&gt;
&lt;br /&gt;
I have been through a number of solution packages getting to where I am, and I am largely still looking. Hopefully I will be able to declare success in the relatively near future...&lt;br /&gt;
&lt;br /&gt;
Like many of you I started out with discrete tools as I was learning how to use Docker. I am not sure how I would get along without this tool. Below I will list what I feel are the strengths and weaknesses of the configurations that I have tried. To a large degree, if you starting out building a home lab, you need to have the ability to manage the configuration that you pt together because generally none of them are a perfect solution. One of the common problems since the developers are likely to be home hobbyists like ourselves, The projects tend to stall out after a while. The developer moves on or experiences people being demanding, ungrateful and sometimes just plain nasty. &lt;br /&gt;
&lt;br /&gt;
I have been willing, and happy, to pay them subscription fees to fund their efforts. These are usually modest, typically equivalent to two or three lattes a month. Getting traction on their part is often difficult and reaching critical mass generally doesn&#039;t happen. I recognize that most of these people have real jobs and developing the code is just a side gig that helps to pay the electric bill and buy some hardware. So, with that, let me tell you how I got to here.&lt;br /&gt;
&lt;br /&gt;
[[The Homelab Stack History]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1556</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1556"/>
		<updated>2026-08-21T00:28:52Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
=Introduction=&lt;br /&gt;
This wiki covers my journey toward the perfect Homelab/Home server for me. This likely is not the perfect one for you. &lt;br /&gt;
&lt;br /&gt;
I am a retired IP professional who spent 45 years in the trenches doing much this same work and got paid for it. Now I do it for fun and, face it, it keeps off of the streets and out of jail. &lt;br /&gt;
&lt;br /&gt;
[[How did I get here?]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1555</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1555"/>
		<updated>2026-08-21T00:23:51Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=User:Lynn&amp;diff=1554</id>
		<title>User:Lynn</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=User:Lynn&amp;diff=1554"/>
		<updated>2026-08-19T01:55:59Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{User&lt;br /&gt;
| full name = Lynn Macey&lt;br /&gt;
| image name =&lt;br /&gt;
| hover text = lynn&lt;br /&gt;
| job title = Happily Retired&lt;br /&gt;
| organization = Working for The Family&lt;br /&gt;
| short quote = General All Around Good Guy&lt;br /&gt;
| about me = Have you got all day?&lt;br /&gt;
| about my work = I spent 45 years in high tech. Now I do it for fun.&lt;br /&gt;
| contact me = lynnmacey@gmail.com&lt;br /&gt;
| disclaimer = Only I am responsible&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
What I need to pull resolution and codec:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,codec_name -of csv=p=0 input.mp4  &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Fetch Termius Passkey: curl -fs https://sshid.io/lynnmacey &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;br /&gt;
&lt;br /&gt;
Get iMatch from these guys: [https://www.photools.com/ Photools.com]&lt;br /&gt;
&lt;br /&gt;
[http://openflights.org/user/lynnmacey Lynn&#039;s Flight History]&lt;br /&gt;
&lt;br /&gt;
[[The Weekly Task List]]&lt;br /&gt;
&lt;br /&gt;
[[Home Server Applications to Try]]&lt;br /&gt;
&lt;br /&gt;
[[The Notes Collection]]&lt;br /&gt;
&lt;br /&gt;
[[Projects]]&lt;br /&gt;
&lt;br /&gt;
[[Wish List and Vendors]]&lt;br /&gt;
&lt;br /&gt;
Some places that may be useful:&lt;br /&gt;
*[https://www.virtualizationhowto.com/2026/07/how-i-organize-my-entire-home-lab-so-i-can-actually-find-anything/ How I Organize My Entire Home Lab]&lt;br /&gt;
[[Videos to Categorize]]&lt;br /&gt;
==Personal Accounts==&lt;br /&gt;
===BofA Checking===&lt;br /&gt;
Routing: 121000358&lt;br /&gt;
Account: 00126220---- add last 4 digits&lt;br /&gt;
===Venmo===&lt;br /&gt;
https://venmo.com/account/sign-in/&lt;br /&gt;
&lt;br /&gt;
 Username: @lynnmacey&lt;br /&gt;
 Password: M...&lt;br /&gt;
 Email Address: lynnmacey@gmail.com&lt;br /&gt;
&lt;br /&gt;
== Time Sensitive Information ==&lt;br /&gt;
&lt;br /&gt;
Interesting .zshrc rc file for the zsh shell. https://github.com/natelandau/dotfiles&lt;br /&gt;
&lt;br /&gt;
Some backup scripts are here https://github.com/natelandau/db_scripts&lt;br /&gt;
&lt;br /&gt;
==Movie reviews ==&lt;br /&gt;
Medieval &amp;quot;porn&amp;quot; : [https://www.youtube.com/watch?v=nTY2_NieZtw]&lt;br /&gt;
&lt;br /&gt;
Banned 1960s movies : [https://www.youtube.com/watch?v=J2CHNxk-jD0]&lt;br /&gt;
&lt;br /&gt;
13 Brutal 1970s Exploitation Films: [https://www.youtube.com/watch?v=3uBYsRYEhO0 13 Brutal 1970s Exploitation Films]&lt;br /&gt;
&lt;br /&gt;
15 Unfiltered Grindhouse Movies Hollywood Refused to Touch: [[https://www.youtube.com/watch?v=kfGhksb6Cho 15 Unfiltered Grindhouse Movies Hollywood Refused to Touch]&lt;br /&gt;
&lt;br /&gt;
[https://www.youtube.com/watch?v=CY_e_9UoQpU 10 Movies So Wild Studios Would Never Greenlight Them Today]&lt;br /&gt;
&lt;br /&gt;
==Lynn&#039;s Flight History==&lt;br /&gt;
&lt;br /&gt;
[http://openflights.org/user/lynnmacey Lynn&#039;s Flight History]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
&amp;lt;a href=&#039;https://openflights.org/user/lynnmacey&#039; target=&#039;_blank&#039;&amp;gt;&amp;lt;img src=&#039;https://openflights.org/banner/lynnmacey.png&#039; width=400 height=70&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want this product for my wall: https://theflightwall.com/products/flightwall-mini-flight-tracking-led-display&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=User:Lynn&amp;diff=1553</id>
		<title>User:Lynn</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=User:Lynn&amp;diff=1553"/>
		<updated>2026-08-18T16:40:02Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* Personal Accounts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{User&lt;br /&gt;
| full name = Lynn Macey&lt;br /&gt;
| image name =&lt;br /&gt;
| hover text = lynn&lt;br /&gt;
| job title = Happily Retired&lt;br /&gt;
| organization = Working for The Family&lt;br /&gt;
| short quote = General All Around Good Guy&lt;br /&gt;
| about me = Have you got all day?&lt;br /&gt;
| about my work = I spent 45 years in high tech. Now I do it for fun.&lt;br /&gt;
| contact me = lynnmacey@gmail.com&lt;br /&gt;
| disclaimer = Only I am responsible&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Fetch Termius Passkey: curl -fs https://sshid.io/lynnmacey &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;br /&gt;
&lt;br /&gt;
Get iMatch from these guys: [https://www.photools.com/ Photools.com]&lt;br /&gt;
&lt;br /&gt;
[http://openflights.org/user/lynnmacey Lynn&#039;s Flight History]&lt;br /&gt;
&lt;br /&gt;
[[The Weekly Task List]]&lt;br /&gt;
&lt;br /&gt;
[[Home Server Applications to Try]]&lt;br /&gt;
&lt;br /&gt;
[[The Notes Collection]]&lt;br /&gt;
&lt;br /&gt;
[[Projects]]&lt;br /&gt;
&lt;br /&gt;
[[Wish List and Vendors]]&lt;br /&gt;
&lt;br /&gt;
Some places that may be useful:&lt;br /&gt;
*[https://www.virtualizationhowto.com/2026/07/how-i-organize-my-entire-home-lab-so-i-can-actually-find-anything/ How I Organize My Entire Home Lab]&lt;br /&gt;
[[Videos to Categorize]]&lt;br /&gt;
==Personal Accounts==&lt;br /&gt;
===BofA Checking===&lt;br /&gt;
Routing: 121000358&lt;br /&gt;
Account: 00126220---- add last 4 digits&lt;br /&gt;
===Venmo===&lt;br /&gt;
https://venmo.com/account/sign-in/&lt;br /&gt;
&lt;br /&gt;
 Username: @lynnmacey&lt;br /&gt;
 Password: M...&lt;br /&gt;
 Email Address: lynnmacey@gmail.com&lt;br /&gt;
&lt;br /&gt;
== Time Sensitive Information ==&lt;br /&gt;
&lt;br /&gt;
Interesting .zshrc rc file for the zsh shell. https://github.com/natelandau/dotfiles&lt;br /&gt;
&lt;br /&gt;
Some backup scripts are here https://github.com/natelandau/db_scripts&lt;br /&gt;
&lt;br /&gt;
==Movie reviews ==&lt;br /&gt;
Medieval &amp;quot;porn&amp;quot; : [https://www.youtube.com/watch?v=nTY2_NieZtw]&lt;br /&gt;
&lt;br /&gt;
Banned 1960s movies : [https://www.youtube.com/watch?v=J2CHNxk-jD0]&lt;br /&gt;
&lt;br /&gt;
13 Brutal 1970s Exploitation Films: [https://www.youtube.com/watch?v=3uBYsRYEhO0 13 Brutal 1970s Exploitation Films]&lt;br /&gt;
&lt;br /&gt;
15 Unfiltered Grindhouse Movies Hollywood Refused to Touch: [[https://www.youtube.com/watch?v=kfGhksb6Cho 15 Unfiltered Grindhouse Movies Hollywood Refused to Touch]&lt;br /&gt;
&lt;br /&gt;
[https://www.youtube.com/watch?v=CY_e_9UoQpU 10 Movies So Wild Studios Would Never Greenlight Them Today]&lt;br /&gt;
&lt;br /&gt;
==Lynn&#039;s Flight History==&lt;br /&gt;
&lt;br /&gt;
[http://openflights.org/user/lynnmacey Lynn&#039;s Flight History]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
&amp;lt;a href=&#039;https://openflights.org/user/lynnmacey&#039; target=&#039;_blank&#039;&amp;gt;&amp;lt;img src=&#039;https://openflights.org/banner/lynnmacey.png&#039; width=400 height=70&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want this product for my wall: https://theflightwall.com/products/flightwall-mini-flight-tracking-led-display&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1552</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1552"/>
		<updated>2026-08-18T01:48:11Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					l, m = string.find(pargs[&#039;image&#039; .. i], &amp;quot;[[file:&amp;quot;)&lt;br /&gt;
					if (l ~= 0) then&lt;br /&gt;
						pargs[&#039;image&#039; .. i] = string.sub(pargs[&#039;image&#039; .. i], m + 1, len(pargs[&#039;image&#039; .. i]) - 2)&lt;br /&gt;
					end&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Living_in_Chico&amp;diff=1551</id>
		<title>Living in Chico</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Living_in_Chico&amp;diff=1551"/>
		<updated>2026-08-18T01:45:45Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* The Apartment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
{{PAGEBANNER:City-of-chico-banner.jpg}}&lt;br /&gt;
=Chico Writeup Related to Retirees=&lt;br /&gt;
[https://familydestinationsguide.com/town-california-affordable-retirees/ Chico Writeup Related to Retirees]&lt;br /&gt;
&lt;br /&gt;
= The Apartment =&lt;br /&gt;
{|&lt;br /&gt;
The apartment is located on the east side of town, approximately here:&lt;br /&gt;
&lt;br /&gt;
{{multiple image&lt;br /&gt;
| footer    = Players are cautioned with a yellow card and sent off with a red card.&lt;br /&gt;
| width     = 450&lt;br /&gt;
| image1    = {{#display_map:39.75224,-121.79213|zoom=100|layers=OpenStreetMap}} &lt;br /&gt;
&lt;br /&gt;
| alt1      = Chico Map&lt;br /&gt;
&lt;br /&gt;
| image2    = Condo beach.jpg&lt;br /&gt;
| width     =450&lt;br /&gt;
| alt2      = Beach at the condo&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Note that the marker is just &amp;quot;close&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The mailing address is: &lt;br /&gt;
&lt;br /&gt;
 The Maceys&lt;br /&gt;
 2580 California Park Dr #223&lt;br /&gt;
 Chico, CA 95928-4213&lt;br /&gt;
&lt;br /&gt;
WiFi quick connect:&lt;br /&gt;
&lt;br /&gt;
[[File:Macey WiFi-1024.jpeg | 150px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Gym code: 25-80-17 (enter)&lt;br /&gt;
&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;br /&gt;
==[[Chico Apartment|Chico Apartment Details]]==&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;br /&gt;
&lt;br /&gt;
==[[The Weekly Task List|What Lynn should be working on by day]]==&lt;br /&gt;
&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;br /&gt;
&lt;br /&gt;
= The House =&lt;br /&gt;
Josh and Anna&#039;s house is on the north end of town, here:&lt;br /&gt;
&lt;br /&gt;
{{#display_map:3163 Burnt Ranch Way, chico, CA&lt;br /&gt;
||width=450|height=500|zoom=100| layers=OpenStreetMap&lt;br /&gt;
}}&lt;br /&gt;
Note that the marker is just &amp;quot;close&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The mailing address is:&lt;br /&gt;
&lt;br /&gt;
 Josh &amp;amp; Anna Macey&lt;br /&gt;
 3163 Sawyers Bar Ln&lt;br /&gt;
 Chico, CA 95973&lt;br /&gt;
&lt;br /&gt;
[[Chico House|Chico House Details]]&lt;br /&gt;
&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1550</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1550"/>
		<updated>2026-08-18T01:45:11Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function die(s)&lt;br /&gt;
  warn(s)&lt;br /&gt;
  error()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					l, m = string.find(pargs[&#039;image&#039; .. i], &amp;quot;[[file:&amp;quot;)&lt;br /&gt;
					if (l == 1) then&lt;br /&gt;
						pargs[&#039;image&#039; .. i] = string.sub(pargs[&#039;image&#039; .. i], m + 1, len(pargs[&#039;image&#039; .. i]) - 2)&lt;br /&gt;
						die(pargs[&#039;image&#039; .. i])&lt;br /&gt;
					end&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Living_in_Chico&amp;diff=1549</id>
		<title>Living in Chico</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Living_in_Chico&amp;diff=1549"/>
		<updated>2026-08-18T01:27:51Z</updated>

		<summary type="html">&lt;p&gt;Lynn: /* The Apartment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
{{PAGEBANNER:City-of-chico-banner.jpg}}&lt;br /&gt;
=Chico Writeup Related to Retirees=&lt;br /&gt;
[https://familydestinationsguide.com/town-california-affordable-retirees/ Chico Writeup Related to Retirees]&lt;br /&gt;
&lt;br /&gt;
= The Apartment =&lt;br /&gt;
{|&lt;br /&gt;
The apartment is located on the east side of town, approximately here:&lt;br /&gt;
&lt;br /&gt;
{{multiple image&lt;br /&gt;
| footer    = Players are cautioned with a yellow card and sent off with a red card.&lt;br /&gt;
| width     = 450&lt;br /&gt;
| image1    = {{#display_map:39.75224,-121.79213|zoom=100|layers=OpenStreetMap}} &lt;br /&gt;
&lt;br /&gt;
| alt1      = Chico Map&lt;br /&gt;
&lt;br /&gt;
| image2    = [[file:Condo beach.jpg]]&lt;br /&gt;
| width     =450&lt;br /&gt;
| alt2      = Beach at the condo&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Note that the marker is just &amp;quot;close&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The mailing address is: &lt;br /&gt;
&lt;br /&gt;
 The Maceys&lt;br /&gt;
 2580 California Park Dr #223&lt;br /&gt;
 Chico, CA 95928-4213&lt;br /&gt;
&lt;br /&gt;
WiFi quick connect:&lt;br /&gt;
&lt;br /&gt;
[[File:Macey WiFi-1024.jpeg | 150px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Gym code: 25-80-17 (enter)&lt;br /&gt;
&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;br /&gt;
==[[Chico Apartment|Chico Apartment Details]]==&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;br /&gt;
&lt;br /&gt;
==[[The Weekly Task List|What Lynn should be working on by day]]==&lt;br /&gt;
&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;br /&gt;
&lt;br /&gt;
= The House =&lt;br /&gt;
Josh and Anna&#039;s house is on the north end of town, here:&lt;br /&gt;
&lt;br /&gt;
{{#display_map:3163 Burnt Ranch Way, chico, CA&lt;br /&gt;
||width=450|height=500|zoom=100| layers=OpenStreetMap&lt;br /&gt;
}}&lt;br /&gt;
Note that the marker is just &amp;quot;close&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The mailing address is:&lt;br /&gt;
&lt;br /&gt;
 Josh &amp;amp; Anna Macey&lt;br /&gt;
 3163 Sawyers Bar Ln&lt;br /&gt;
 Chico, CA 95973&lt;br /&gt;
&lt;br /&gt;
[[Chico House|Chico House Details]]&lt;br /&gt;
&lt;br /&gt;
Return to: {{Highlight|[[Main Page]]}}&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1548</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1548"/>
		<updated>2026-08-18T01:26:37Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					l, m = string.find(pargs[&#039;image&#039; .. i], &amp;quot;[[file:&amp;quot;)&lt;br /&gt;
					if (l == 1) then&lt;br /&gt;
						pargs[&#039;image&#039; .. i] = string.sub(pargs[&#039;image&#039; .. i], m + 1, len(pargs[&#039;image&#039; .. i]) - 2)&lt;br /&gt;
					end&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1547</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1547"/>
		<updated>2026-08-18T01:21:31Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					l, m = string.find(pargs[&#039;image&#039; .. i], &amp;quot;[[file:&amp;quot;)&lt;br /&gt;
					if (l == 1) then&lt;br /&gt;
						pargs[&#039;image&#039; .. i] = string.sub(pargs[&#039;image&#039; .. i], m, len(pargs[&#039;image&#039; .. i]) - 2)&lt;br /&gt;
					end&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1546</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1546"/>
		<updated>2026-08-18T01:09:40Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					l, m = string.find(pargs[&#039;image&#039; .. i], &amp;quot;[[file:&amp;quot;)&lt;br /&gt;
					if (l == 1) then&lt;br /&gt;
						pargs[&#039;image&#039; .. i] = string.sub(pargs[&#039;image&#039; .. i], l, len(pargs[&#039;image&#039; .. i]) - 2)&lt;br /&gt;
					end&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1545</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1545"/>
		<updated>2026-08-18T01:08:51Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					l, m = string.find(pargs[&#039;image&#039; .. i], &amp;quot;[[File:&amp;quot;)&lt;br /&gt;
					if (l == 1) then&lt;br /&gt;
						pargs[&#039;image&#039; .. i] = string.sub(pargs[&#039;image&#039; .. i], l, len(pargs[&#039;image&#039; .. i]) - 2)&lt;br /&gt;
					end&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1544</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1544"/>
		<updated>2026-08-18T00:46:12Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1543</id>
		<title>Module:Multiple image</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Multiple_image&amp;diff=1543"/>
		<updated>2026-08-18T00:12:18Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- implements [[template:multiple image]]&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local autoscaledimages&lt;br /&gt;
local nonautoscaledimages&lt;br /&gt;
&lt;br /&gt;
local function isnotempty(s)&lt;br /&gt;
	return s and s:match( &#039;^%s*(.-)%s*$&#039; ) ~= &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removepx(s)&lt;br /&gt;
	return tostring(s or &#039;&#039;):match(&#039;^(.*)[Pp][Xx]%s*$&#039;) or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getdimensions(s, w, h)&lt;br /&gt;
	if tonumber(w) and tonumber(h) then&lt;br /&gt;
		nonautoscaledimages = true&lt;br /&gt;
		return tonumber(w), tonumber(h)&lt;br /&gt;
	end&lt;br /&gt;
	local file = s and mw.title.new(&#039;File:&#039; .. mw.uri.decode(mw.ustring.gsub(s,&#039;%|.*$&#039;,&#039;&#039;), &#039;WIKI&#039;))&lt;br /&gt;
	file = file and file.file or {width = 0, height = 0}&lt;br /&gt;
	w = tonumber(file.width) or 0&lt;br /&gt;
	h = tonumber(file.height) or 0&lt;br /&gt;
	autoscaledimages = true&lt;br /&gt;
	return w, h&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderImageCell(image, width, height, link, alt, thumbtime, caption, textalign, istyle)&lt;br /&gt;
	local root = mw.html.create(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local altstr = &#039;|alt=&#039; .. (alt or &#039;&#039;)&lt;br /&gt;
	local linkstr = link and (&#039;|link=&#039; .. link) or &#039;&#039;&lt;br /&gt;
	local widthstr = &#039;|&#039; .. tostring(width) .. &#039;px&#039;&lt;br /&gt;
	local thumbtimestr = &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
	if isnotempty( thumbtime ) then&lt;br /&gt;
		thumbtimestr = &#039;|thumbtime=&#039; .. thumbtime&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local imagediv = root:tag(&#039;div&#039;)&lt;br /&gt;
	imagediv:addClass(&#039;thumbimage&#039;)&lt;br /&gt;
	imagediv:cssText(istyle)&lt;br /&gt;
	if( height ) then&lt;br /&gt;
		imagediv:css(&#039;height&#039;, tostring(height) .. &#039;px&#039;)&lt;br /&gt;
		imagediv:css(&#039;overflow&#039;, &#039;hidden&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	imagediv:wikitext(&#039;[[file:&#039; .. image .. widthstr .. linkstr .. altstr .. thumbtimestr .. &#039;]]&#039;)&lt;br /&gt;
	if isnotempty(caption) then&lt;br /&gt;
		local captiondiv = root:tag(&#039;div&#039;)&lt;br /&gt;
		captiondiv:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
		if isnotempty(textalign) then&lt;br /&gt;
			captiondiv:addClass(&#039;text-align-&#039; .. textalign)&lt;br /&gt;
		end&lt;br /&gt;
		captiondiv:wikitext(caption)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getWidth(w1, w2)&lt;br /&gt;
	local w&lt;br /&gt;
	if isnotempty(w1) then&lt;br /&gt;
		w = tonumber(w1)&lt;br /&gt;
	elseif isnotempty(w2) then&lt;br /&gt;
		w = tonumber(w2)&lt;br /&gt;
	end&lt;br /&gt;
	return w or 200&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPerRow(pstr, ic)&lt;br /&gt;
	-- split string into array using any non-digit as a dilimiter&lt;br /&gt;
	local pr = mw.text.split(pstr or &#039;&#039;, &#039;[^%d][^%d]*&#039;)&lt;br /&gt;
	-- if split failed, assume a single row&lt;br /&gt;
	if (#pr &amp;lt; 1) then&lt;br /&gt;
		pr = {tostring(ic)}&lt;br /&gt;
	end&lt;br /&gt;
	-- convert the array of strings to an array of numbers,&lt;br /&gt;
	-- adding any implied/missing numbers at the end of the array&lt;br /&gt;
	local r = 1&lt;br /&gt;
	local thisrow = tonumber(pr[1] or ic) or ic&lt;br /&gt;
	local prownum = {}&lt;br /&gt;
	while( ic &amp;gt; 0 ) do&lt;br /&gt;
		prownum[r] = thisrow&lt;br /&gt;
		ic = ic - thisrow&lt;br /&gt;
		r = r + 1&lt;br /&gt;
		-- use the previous if the next is missing and&lt;br /&gt;
		-- make sure we don&#039;t overstep the number of images&lt;br /&gt;
		thisrow = math.min(tonumber(pr[r] or thisrow) or ic, ic)&lt;br /&gt;
	end&lt;br /&gt;
	return prownum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderMultipleImages(frame)&lt;br /&gt;
	local pargs = frame:getParent().args&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local width = removepx(pargs[&#039;width&#039;] or &#039;&#039;)&lt;br /&gt;
	local dir = pargs[&#039;direction&#039;] or &#039;&#039;&lt;br /&gt;
	local border = pargs[&#039;border&#039;] or args[&#039;border&#039;] or &#039;&#039;&lt;br /&gt;
	local align = pargs[&#039;align&#039;] or args[&#039;align&#039;] or (border == &#039;infobox&#039; and &#039;center&#039; or &#039;&#039;)&lt;br /&gt;
	local capalign = pargs[&#039;caption_align&#039;] or args[&#039;caption_align&#039;] or &#039;&#039;&lt;br /&gt;
	local totalwidth = removepx(pargs[&#039;total_width&#039;] or args[&#039;total_width&#039;] or &#039;&#039;)&lt;br /&gt;
	local imgstyle = pargs[&#039;image_style&#039;] or args[&#039;image_style&#039;]&lt;br /&gt;
	local header = pargs[&#039;header&#039;] or pargs[&#039;title&#039;] or &#039;&#039;&lt;br /&gt;
	local footer = pargs[&#039;footer&#039;] or &#039;&#039;&lt;br /&gt;
	local imagegap = tonumber(pargs[&#039;image_gap&#039;] or &#039;1&#039;) or 1&lt;br /&gt;
	local perrow = nil&lt;br /&gt;
	local thumbclass = {&lt;br /&gt;
		[&amp;quot;left&amp;quot;] = &#039;tleft&#039;,&lt;br /&gt;
		[&amp;quot;none&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;center&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;centre&amp;quot;] = &#039;tnone&#039;,&lt;br /&gt;
		[&amp;quot;right&amp;quot;] = &#039;tright&#039;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	-- find all the nonempty images&lt;br /&gt;
	local imagenumbers = {}&lt;br /&gt;
	local imagecount = 0&lt;br /&gt;
	for k, v in pairs( pargs ) do&lt;br /&gt;
		local i = tonumber(tostring(k):match( &#039;^%s*image([%d]+)%s*$&#039; ) or &#039;0&#039;)&lt;br /&gt;
		if( i &amp;gt; 0 and isnotempty(v) ) then&lt;br /&gt;
			table.insert( imagenumbers, i)&lt;br /&gt;
			imagecount = imagecount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- sort the imagenumbers&lt;br /&gt;
	table.sort(imagenumbers)&lt;br /&gt;
&lt;br /&gt;
	-- create an array with the number of images per row&lt;br /&gt;
	perrow = getPerRow(dir == &#039;vertical&#039; and &#039;1&#039; or pargs[&#039;perrow&#039;], imagecount)&lt;br /&gt;
&lt;br /&gt;
	-- compute the number of rows&lt;br /&gt;
	local rowcount = #perrow&lt;br /&gt;
&lt;br /&gt;
	-- store the image widths and compute row widths and maximum row width&lt;br /&gt;
	local heights = {}&lt;br /&gt;
	local widths = {}&lt;br /&gt;
	local widthmax = 0&lt;br /&gt;
	local widthsum = {}&lt;br /&gt;
	local k = 0&lt;br /&gt;
	for r=1,rowcount do&lt;br /&gt;
		widthsum[r] = 0&lt;br /&gt;
		for c=1,perrow[r] do&lt;br /&gt;
			k = k + 1&lt;br /&gt;
			if( k &amp;lt;= imagecount ) then&lt;br /&gt;
				local i = imagenumbers[k]&lt;br /&gt;
				if( isnotempty(totalwidth) ) then&lt;br /&gt;
					print(&amp;quot;Image&amp;quot;, pargs[&#039;image&#039; .. i])&lt;br /&gt;
					widths[k], heights[k] = getdimensions(pargs[&#039;image&#039; .. i], pargs[&#039;width&#039; .. i], pargs[&#039;height&#039; .. i])&lt;br /&gt;
				else&lt;br /&gt;
					widths[k] = getWidth(width, pargs[&#039;width&#039; .. i])&lt;br /&gt;
				end&lt;br /&gt;
				widthsum[r] = widthsum[r] + widths[k]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- make sure the gap is non-negative&lt;br /&gt;
	if imagegap &amp;lt; 0 then imagegap = 0 end&lt;br /&gt;
&lt;br /&gt;
	-- if total_width has been specified, rescale the image widths&lt;br /&gt;
	if( isnotempty(totalwidth) ) then&lt;br /&gt;
		totalwidth = tonumber(totalwidth)&lt;br /&gt;
		widthmax = 0&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local koffset = k&lt;br /&gt;
			local tw = totalwidth - (3 + imagegap) * (perrow[r] - 1) - 12&lt;br /&gt;
			local ar = {}&lt;br /&gt;
			local arsum = 0&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local h = heights[k] or 0&lt;br /&gt;
					if (h &amp;gt; 0) then&lt;br /&gt;
						ar[j] = widths[k]/h&lt;br /&gt;
						heights[k] = h&lt;br /&gt;
					else&lt;br /&gt;
						ar[j] = widths[k]/100&lt;br /&gt;
					end&lt;br /&gt;
					arsum = arsum + ar[j]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local ht = tw/arsum&lt;br /&gt;
			local ws = 0&lt;br /&gt;
			k = koffset&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k&amp;lt;= imagecount ) then&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					widths[k] = math.floor(ar[j]*ht + 0.5)&lt;br /&gt;
					ws = ws + widths[k]&lt;br /&gt;
					if heights[k] then&lt;br /&gt;
						heights[k] = math.floor(ht)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			widthsum[r] = ws&lt;br /&gt;
			widthmax = math.max(widthmax, widthsum[r])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- start building the array of images, if there are images&lt;br /&gt;
	if( imagecount &amp;gt; 0 ) then&lt;br /&gt;
		-- compute width of outer div&lt;br /&gt;
		local bodywidth = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			if( widthmax == widthsum[r] ) then&lt;br /&gt;
				bodywidth = widthmax + (3 + imagegap) * (perrow[r] - 1) + 12&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- The body has a min-width of 100, which needs to be taken into account on specific widths&lt;br /&gt;
		bodywidth = math.max( 100, bodywidth - 8);&lt;br /&gt;
&lt;br /&gt;
		local bg = pargs[&#039;background color&#039;] or &#039;&#039;&lt;br /&gt;
		-- create the array of images&lt;br /&gt;
		local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
		root:addClass(&#039;thumb&#039;)&lt;br /&gt;
		root:addClass(&#039;tmulti&#039;)&lt;br /&gt;
		-- root:addClass(&#039;tmulti-sandbox&#039;)&lt;br /&gt;
		root:addClass(thumbclass[align] or &#039;tright&#039;)&lt;br /&gt;
&lt;br /&gt;
		if( align == &#039;center&#039; or align == &#039;centre&#039; ) then&lt;br /&gt;
			root:addClass(&#039;center&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			root:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local div = root:tag(&#039;div&#039;)&lt;br /&gt;
		div:addClass(&#039;thumbinner&#039;)&lt;br /&gt;
		div:css(&#039;width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
			:css(&#039;max-width&#039;, tostring(bodywidth) .. &#039;px&#039;)&lt;br /&gt;
		if( bg ~= &#039;&#039; ) then&lt;br /&gt;
			div:css(&#039;background-color&#039;, bg)&lt;br /&gt;
		end&lt;br /&gt;
		if( border == &#039;infobox&#039; or border == &#039;none&#039;) then&lt;br /&gt;
			div:css(&#039;border&#039;, &#039;none&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		-- add the header&lt;br /&gt;
		if( isnotempty(header) ) then&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;theader&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, pargs[&#039;header_align&#039;])&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;header_background&#039;])&lt;br /&gt;
					:wikitext(header)&lt;br /&gt;
		end&lt;br /&gt;
		-- loop through the images&lt;br /&gt;
		local k = 0&lt;br /&gt;
		for r=1,rowcount do&lt;br /&gt;
			local rowdiv = div:tag(&#039;div&#039;):addClass(&#039;trow&#039;);&lt;br /&gt;
			for j=1,perrow[r] do&lt;br /&gt;
				k = k + 1&lt;br /&gt;
				if( k &amp;lt;= imagecount ) then&lt;br /&gt;
					local imagediv = rowdiv:tag(&#039;div&#039;)&lt;br /&gt;
					imagediv:addClass(&#039;tsingle&#039;)&lt;br /&gt;
					if bg ~= &#039;&#039; then&lt;br /&gt;
						imagediv:css(&#039;background-color&#039;, bg);&lt;br /&gt;
					end&lt;br /&gt;
					if ((imagegap &amp;gt; 1) and (j &amp;lt; perrow[r])) then&lt;br /&gt;
						imagediv:css(&#039;margin-right&#039;, tostring(imagegap) .. &#039;px&#039;)&lt;br /&gt;
					end&lt;br /&gt;
					local i = imagenumbers[k]&lt;br /&gt;
					local img = pargs[&#039;image&#039; .. i]&lt;br /&gt;
					local w = widths[k]&lt;br /&gt;
					imagediv:css(&#039;width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
						:css(&#039;max-width&#039;, tostring(2 + w) .. &#039;px&#039;)&lt;br /&gt;
					imagediv:wikitext(renderImageCell(img, w, heights[k],&lt;br /&gt;
						pargs[&#039;link&#039; .. i], pargs[&#039;alt&#039; .. i],&lt;br /&gt;
						pargs[&#039;thumbtime&#039; .. i], pargs[&#039;caption&#039; .. i], capalign, imgstyle))&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- add the footer&lt;br /&gt;
		if( isnotempty(footer) ) then&lt;br /&gt;
			local falign = string.lower(pargs[&#039;footer_align&#039;] or args[&#039;footer_align&#039;] or &#039;left&#039;)&lt;br /&gt;
			falign = (falign == &#039;centre&#039;) and &#039;center&#039; or falign&lt;br /&gt;
			div:tag(&#039;div&#039;)&lt;br /&gt;
				:addClass(&#039;trow&#039;)&lt;br /&gt;
				:css(&#039;display&#039;, (falign ~= &#039;left&#039;) and &#039;flow-root&#039; or &#039;flex&#039;)&lt;br /&gt;
				:tag(&#039;div&#039;)&lt;br /&gt;
					:addClass(&#039;thumbcaption&#039;)&lt;br /&gt;
					:css(&#039;text-align&#039;, (falign ~= &#039;left&#039;) and falign or nil)&lt;br /&gt;
					:css(&#039;background-color&#039;, pargs[&#039;footer_background&#039;])&lt;br /&gt;
					:wikitext(footer)&lt;br /&gt;
		end&lt;br /&gt;
		return tostring(root)&lt;br /&gt;
	end&lt;br /&gt;
	return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render( frame )&lt;br /&gt;
	autoscaledimages = false&lt;br /&gt;
	nonautoscaledimages = false&lt;br /&gt;
&lt;br /&gt;
	return frame:extensionTag {name = &#039;templatestyles&#039;, args = {src = &#039;Multiple image/styles.css&#039;, wrapper = &amp;quot;.tmulti&amp;quot;}}&lt;br /&gt;
		.. renderMultipleImages( frame )&lt;br /&gt;
		.. (autoscaledimages and &#039;[[Category:Pages using multiple image with auto scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
		.. (nonautoscaledimages and &#039;[[Category:Pages using multiple image with manual scaled images]]&#039; or &#039;&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1542</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1542"/>
		<updated>2026-08-17T22:56:13Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[File:Chico_banner.webp]]&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
This is a collection of information related to, and being, &amp;quot;The Maceys&amp;quot;. It is intended to be a collection point for things that do not have a security value. We have other places to store passwords, for example.&lt;br /&gt;
&lt;br /&gt;
The primary focus is the Maceys in California who are Lynn and Bonita, their kids and kid&#039;s spouses. You will find them here:&lt;br /&gt;
{{col-float}}&lt;br /&gt;
==[[The Family Guide]]==&lt;br /&gt;
==[[What&#039;s new on Plex?]]==&lt;br /&gt;
==Quick Links==&lt;br /&gt;
==[[Chico Technical|Chico General Information]]==&lt;br /&gt;
&lt;br /&gt;
==[[San Jose General Information|San Jose General Information]]==&lt;br /&gt;
&lt;br /&gt;
==[[Technical Guides|Technical Guides]]==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{col-float-break}}&lt;br /&gt;
{{#display_map: Chico~Chico [[Living in Chico]]; San Jose~San Jose [[San Jose]]; Lake Almanor~Lake Almanor [[Lake Almanor]];Lihue~Nalu [[Hawaii Condo]]&lt;br /&gt;
|width=350|height=400|layers=OpenStreetMap}}&lt;br /&gt;
&lt;br /&gt;
{{col-float-end}}&lt;br /&gt;
==Family Locations==&lt;br /&gt;
&#039;&#039;&#039;{{Highlight|[[Living in Chico]]}}&#039;&#039;&#039; - [https://www.flightradar24.com/39.81,-122.13/9 Display Flight Radar]&lt;br /&gt;
&lt;br /&gt;
[https://adsb-chico.maceys.net/ Aircraft Localto Both Sites]&lt;br /&gt;
&lt;br /&gt;
[[San Jose Information]] - [https://www.flightradar24.com/37.37,-121.85/11 Display Flight Radar]&lt;br /&gt;
&lt;br /&gt;
[[Lake Almanor Information]] - [https://www.flightradar24.com/40.23,-120.98/10 DisplayFlight Radar]&lt;br /&gt;
[https://adsb-chico.maceys.net/1 Local Aircraft]&lt;br /&gt;
&lt;br /&gt;
[[Kauai Condotel]]&lt;br /&gt;
&lt;br /&gt;
[[Mace Companies]]&lt;br /&gt;
&lt;br /&gt;
==Special Interest==&lt;br /&gt;
[[Live Flight Radar]]&lt;br /&gt;
&lt;br /&gt;
[[ Interactive Fiction]]&lt;br /&gt;
&lt;br /&gt;
[[Cruises| Cruises Taken]]&lt;br /&gt;
&lt;br /&gt;
[[Software#Ring]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1541</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1541"/>
		<updated>2026-08-17T22:55:44Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[File:Chico_banner.webp]]&lt;br /&gt;
Hook extensions @ [[Main Page|MediaWiki.org]] &amp;gt; [[:Category:Extensions|Extensions]] &amp;gt;&lt;br /&gt;
= Introduction =&lt;br /&gt;
This is a collection of information related to, and being, &amp;quot;The Maceys&amp;quot;. It is intended to be a collection point for things that do not have a security value. We have other places to store passwords, for example.&lt;br /&gt;
&lt;br /&gt;
The primary focus is the Maceys in California who are Lynn and Bonita, their kids and kid&#039;s spouses. You will find them here:&lt;br /&gt;
{{col-float}}&lt;br /&gt;
==[[The Family Guide]]==&lt;br /&gt;
==[[What&#039;s new on Plex?]]==&lt;br /&gt;
==Quick Links==&lt;br /&gt;
==[[Chico Technical|Chico General Information]]==&lt;br /&gt;
&lt;br /&gt;
==[[San Jose General Information|San Jose General Information]]==&lt;br /&gt;
&lt;br /&gt;
==[[Technical Guides|Technical Guides]]==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{col-float-break}}&lt;br /&gt;
{{#display_map: Chico~Chico [[Living in Chico]]; San Jose~San Jose [[San Jose]]; Lake Almanor~Lake Almanor [[Lake Almanor]];Lihue~Nalu [[Hawaii Condo]]&lt;br /&gt;
|width=350|height=400|layers=OpenStreetMap}}&lt;br /&gt;
&lt;br /&gt;
{{col-float-end}}&lt;br /&gt;
==Family Locations==&lt;br /&gt;
&#039;&#039;&#039;{{Highlight|[[Living in Chico]]}}&#039;&#039;&#039; - [https://www.flightradar24.com/39.81,-122.13/9 Display Flight Radar]&lt;br /&gt;
&lt;br /&gt;
[https://adsb-chico.maceys.net/ Aircraft Localto Both Sites]&lt;br /&gt;
&lt;br /&gt;
[[San Jose Information]] - [https://www.flightradar24.com/37.37,-121.85/11 Display Flight Radar]&lt;br /&gt;
&lt;br /&gt;
[[Lake Almanor Information]] - [https://www.flightradar24.com/40.23,-120.98/10 DisplayFlight Radar]&lt;br /&gt;
[https://adsb-chico.maceys.net/1 Local Aircraft]&lt;br /&gt;
&lt;br /&gt;
[[Kauai Condotel]]&lt;br /&gt;
&lt;br /&gt;
[[Mace Companies]]&lt;br /&gt;
&lt;br /&gt;
==Special Interest==&lt;br /&gt;
[[Live Flight Radar]]&lt;br /&gt;
&lt;br /&gt;
[[ Interactive Fiction]]&lt;br /&gt;
&lt;br /&gt;
[[Cruises| Cruises Taken]]&lt;br /&gt;
&lt;br /&gt;
[[Software#Ring]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1540</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Main_Page&amp;diff=1540"/>
		<updated>2026-08-17T22:55:21Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[File:Chico_banner.webp]]&lt;br /&gt;
#* Hook extensions @ [[Main Page|MediaWiki.org]] &amp;gt; [[:Category:Extensions|Extensions]] &amp;gt;&lt;br /&gt;
= Introduction =&lt;br /&gt;
This is a collection of information related to, and being, &amp;quot;The Maceys&amp;quot;. It is intended to be a collection point for things that do not have a security value. We have other places to store passwords, for example.&lt;br /&gt;
&lt;br /&gt;
The primary focus is the Maceys in California who are Lynn and Bonita, their kids and kid&#039;s spouses. You will find them here:&lt;br /&gt;
{{col-float}}&lt;br /&gt;
==[[The Family Guide]]==&lt;br /&gt;
==[[What&#039;s new on Plex?]]==&lt;br /&gt;
==Quick Links==&lt;br /&gt;
==[[Chico Technical|Chico General Information]]==&lt;br /&gt;
&lt;br /&gt;
==[[San Jose General Information|San Jose General Information]]==&lt;br /&gt;
&lt;br /&gt;
==[[Technical Guides|Technical Guides]]==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{col-float-break}}&lt;br /&gt;
{{#display_map: Chico~Chico [[Living in Chico]]; San Jose~San Jose [[San Jose]]; Lake Almanor~Lake Almanor [[Lake Almanor]];Lihue~Nalu [[Hawaii Condo]]&lt;br /&gt;
|width=350|height=400|layers=OpenStreetMap}}&lt;br /&gt;
&lt;br /&gt;
{{col-float-end}}&lt;br /&gt;
==Family Locations==&lt;br /&gt;
&#039;&#039;&#039;{{Highlight|[[Living in Chico]]}}&#039;&#039;&#039; - [https://www.flightradar24.com/39.81,-122.13/9 Display Flight Radar]&lt;br /&gt;
&lt;br /&gt;
[https://adsb-chico.maceys.net/ Aircraft Localto Both Sites]&lt;br /&gt;
&lt;br /&gt;
[[San Jose Information]] - [https://www.flightradar24.com/37.37,-121.85/11 Display Flight Radar]&lt;br /&gt;
&lt;br /&gt;
[[Lake Almanor Information]] - [https://www.flightradar24.com/40.23,-120.98/10 DisplayFlight Radar]&lt;br /&gt;
[https://adsb-chico.maceys.net/1 Local Aircraft]&lt;br /&gt;
&lt;br /&gt;
[[Kauai Condotel]]&lt;br /&gt;
&lt;br /&gt;
[[Mace Companies]]&lt;br /&gt;
&lt;br /&gt;
==Special Interest==&lt;br /&gt;
[[Live Flight Radar]]&lt;br /&gt;
&lt;br /&gt;
[[ Interactive Fiction]]&lt;br /&gt;
&lt;br /&gt;
[[Cruises| Cruises Taken]]&lt;br /&gt;
&lt;br /&gt;
[[Software#Ring]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Template_link_general/config&amp;diff=1539</id>
		<title>Module:Template link general/config</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Template_link_general/config&amp;diff=1539"/>
		<updated>2026-08-17T22:19:12Z</updated>

		<summary type="html">&lt;p&gt;Lynn: Created page with &amp;quot;local cfg = {}; -- Don’t touch this line.  -- The link for the “subst:” prefix when the “link subst” parameter is truthy. -- Set to “nil” to disable  the “link subst” parameter. cfg[&amp;quot;subst-link&amp;quot;] = &amp;quot;Special:MyLanguage/Help:Substitution&amp;quot;;  return cfg; -- Don’t touch this line.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local cfg = {}; -- Don’t touch this line.&lt;br /&gt;
&lt;br /&gt;
-- The link for the “subst:” prefix when the “link subst” parameter is truthy.&lt;br /&gt;
-- Set to “nil” to disable  the “link subst” parameter.&lt;br /&gt;
cfg[&amp;quot;subst-link&amp;quot;] = &amp;quot;Special:MyLanguage/Help:Substitution&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
return cfg; -- Don’t touch this line.&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Module:Template_link_general&amp;diff=1538</id>
		<title>Module:Template link general</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Module:Template_link_general&amp;diff=1538"/>
		<updated>2026-08-17T22:16:14Z</updated>

		<summary type="html">&lt;p&gt;Lynn: Created page with &amp;quot;-- This implements Template:Template link general and various other templates in its family local getArgs = require(&amp;#039;Module:Arguments&amp;#039;).getArgs local yesno = require(&amp;#039;Module:Yesno&amp;#039;)  local cfg = mw.loadData(&amp;#039;Module:Template link general/config&amp;#039;)  local p = {}  -- Is a string non-empty? local function _ne(s) 	return s ~= nil and s ~= &amp;quot;&amp;quot; end  local nw = mw.text.nowiki  local function addTemplate(s) 	local i, _ = s:find(&amp;#039;:&amp;#039;, 1, true) 	if i == nil then 		return &amp;#039;Template...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- This implements [[Template:Template link general]] and various other templates in its family&lt;br /&gt;
local getArgs = require(&#039;Module:Arguments&#039;).getArgs&lt;br /&gt;
local yesno = require(&#039;Module:Yesno&#039;)&lt;br /&gt;
&lt;br /&gt;
local cfg = mw.loadData(&#039;Module:Template link general/config&#039;)&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
-- Is a string non-empty?&lt;br /&gt;
local function _ne(s)&lt;br /&gt;
	return s ~= nil and s ~= &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local nw = mw.text.nowiki&lt;br /&gt;
&lt;br /&gt;
local function addTemplate(s)&lt;br /&gt;
	local i, _ = s:find(&#039;:&#039;, 1, true)&lt;br /&gt;
	if i == nil then&lt;br /&gt;
		return &#039;Template:&#039; .. s&lt;br /&gt;
	end&lt;br /&gt;
	local ns = s:sub(1, i - 1)&lt;br /&gt;
	if ns == &#039;&#039; or mw.site.namespaces[ns] then&lt;br /&gt;
		return s&lt;br /&gt;
	else&lt;br /&gt;
		return &#039;Template:&#039; .. s&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function trimTemplate(s)&lt;br /&gt;
	local needle = &#039;template:&#039;&lt;br /&gt;
	if s:sub(1, needle:len()):lower() == needle then&lt;br /&gt;
		return s:sub(needle:len() + 1)&lt;br /&gt;
	else&lt;br /&gt;
		return s&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function linkTitle(args)&lt;br /&gt;
	if yesno(args.nolink) then&lt;br /&gt;
		return args[1]&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local titleObj&lt;br /&gt;
	local titlePart = &#039;[[&#039;&lt;br /&gt;
	if args[1] then&lt;br /&gt;
		-- This handles :Page and other NS&lt;br /&gt;
		titleObj = mw.title.new(args[1], &#039;Template&#039;)&lt;br /&gt;
	else&lt;br /&gt;
		titleObj = mw.title.getCurrentTitle()&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	titlePart = titlePart .. (titleObj ~= nil and titleObj.fullText or&lt;br /&gt;
				addTemplate(args[1]))&lt;br /&gt;
&lt;br /&gt;
	local textPart = args.alttext&lt;br /&gt;
	if not _ne(textPart) then&lt;br /&gt;
		if titleObj ~= nil then&lt;br /&gt;
			textPart = titleObj:inNamespace(&amp;quot;Template&amp;quot;) and args[1] or titleObj.fullText&lt;br /&gt;
		else&lt;br /&gt;
			-- redlink&lt;br /&gt;
			textPart = args[1]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if yesno(args.brace) then&lt;br /&gt;
		textPart = nw(&#039;{{&#039;) .. textPart .. nw(&#039;}}&#039;)&lt;br /&gt;
	elseif yesno(args.braceinside) then&lt;br /&gt;
		textPart = nw(&#039;{&#039;) .. textPart .. nw(&#039;}&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	titlePart = titlePart .. &#039;|&#039; .. textPart .. &#039;]]&#039;&lt;br /&gt;
	if yesno(args.braceinside) then&lt;br /&gt;
		titlePart = nw(&#039;{&#039;) .. titlePart .. nw(&#039;}&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	return titlePart&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = getArgs(frame, {&lt;br /&gt;
		trim = true,&lt;br /&gt;
		removeBlanks = false,&lt;br /&gt;
		frameOnly = false,&lt;br /&gt;
		wrappers = {&lt;br /&gt;
			&amp;quot;Template:Tlg&amp;quot;,&lt;br /&gt;
			&amp;quot;Template:Template link general&amp;quot;,&lt;br /&gt;
		},&lt;br /&gt;
	})&lt;br /&gt;
	return p._main(args)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p._main(args)&lt;br /&gt;
	-- TemplateStyles&lt;br /&gt;
	local templateStyles = {&lt;br /&gt;
		mono   = false,&lt;br /&gt;
		nowrap = false,&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	local bold = yesno(args.bold) or yesno(args.boldlink) or yesno(args.boldname)&lt;br /&gt;
	local italic = yesno(args.italic) or yesno(args.italics)&lt;br /&gt;
	local dontBrace = yesno(args.brace) or yesno(args.braceinside)&lt;br /&gt;
	local code = yesno(args.code) or yesno(args.tt)&lt;br /&gt;
	local show_result = yesno(args._show_result)&lt;br /&gt;
	local expand = yesno(args._expand)&lt;br /&gt;
	local classes = {}&lt;br /&gt;
&lt;br /&gt;
	-- Build the link part&lt;br /&gt;
	local titlePart = linkTitle(args)&lt;br /&gt;
	if bold then titlePart = &amp;quot;&#039;&#039;&#039;&amp;quot; .. titlePart .. &amp;quot;&#039;&#039;&#039;&amp;quot; end&lt;br /&gt;
&lt;br /&gt;
	if yesno(args.subst) then&lt;br /&gt;
		local substLink = cfg[&#039;subst-link&#039;]&lt;br /&gt;
		if _ne(substLink) and yesno(args[&#039;link subst&#039;]) then&lt;br /&gt;
			titlePart = &#039;[[&#039; .. substLink .. &#039;|subst]]:&#039; .. titlePart&lt;br /&gt;
		else&lt;br /&gt;
			titlePart = &#039;subst:&#039; .. titlePart&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if yesno(args.nowrapname) then&lt;br /&gt;
		templateStyles.nowrap = true&lt;br /&gt;
		titlePart = &#039;&amp;lt;span class=&amp;quot;nowrap&amp;quot;&amp;gt;&#039; .. titlePart .. &#039;&amp;lt;/span&amp;gt;&#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Build the arguments&lt;br /&gt;
	local textPart = &amp;quot;&amp;quot;&lt;br /&gt;
	local textPartBuffer = &amp;quot;&amp;amp;#124;&amp;quot;&lt;br /&gt;
	local codeArguments = {}&lt;br /&gt;
	local codeArgumentsString = &amp;quot;&amp;quot;&lt;br /&gt;
	local i = 2&lt;br /&gt;
	local j = 1&lt;br /&gt;
	while args[i] do&lt;br /&gt;
		local val = args[i]&lt;br /&gt;
		if val ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			if yesno(args.nowiki) then&lt;br /&gt;
				-- Unstrip nowiki tags first because calling nw on something that already contains nowiki tags will&lt;br /&gt;
				-- mangle the nowiki strip marker and result in literal UNIQ...QINU showing up&lt;br /&gt;
				val = nw(mw.text.unstripNoWiki(val))&lt;br /&gt;
			end&lt;br /&gt;
			local k, v = string.match(val, &amp;quot;(.*)=(.*)&amp;quot;)&lt;br /&gt;
			if not k then&lt;br /&gt;
				codeArguments[j] = val&lt;br /&gt;
				j = j + 1&lt;br /&gt;
			else&lt;br /&gt;
				codeArguments[k] = v&lt;br /&gt;
			end&lt;br /&gt;
			codeArgumentsString = codeArgumentsString .. textPartBuffer .. val&lt;br /&gt;
			if italic then&lt;br /&gt;
				val = &#039;&amp;lt;span style=&amp;quot;font-style:italic;&amp;quot;&amp;gt;&#039; .. val .. &#039;&amp;lt;/span&amp;gt;&#039;&lt;br /&gt;
			end&lt;br /&gt;
			textPart = textPart .. textPartBuffer .. val&lt;br /&gt;
		end&lt;br /&gt;
		i = i + 1&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local tagName = nil&lt;br /&gt;
	local css = nil&lt;br /&gt;
&lt;br /&gt;
	-- final wrap&lt;br /&gt;
	local ret = titlePart .. textPart&lt;br /&gt;
	if not dontBrace then ret = nw(&#039;{{&#039;) .. ret .. nw(&#039;}}&#039;) end&lt;br /&gt;
	if yesno(args.a) then ret = nw(&#039;*&#039;) .. &#039;&amp;amp;nbsp;&#039; .. ret end&lt;br /&gt;
	if yesno(args.kbd) then&lt;br /&gt;
		tagName = &#039;kbd&#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if yesno(args.mono) then&lt;br /&gt;
		templateStyles.mono = true&lt;br /&gt;
		table.insert(classes, &#039;monospaced&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local plaincode = yesno(args.plaincode) and not code&lt;br /&gt;
	if code or plaincode then&lt;br /&gt;
		if tagName then&lt;br /&gt;
			-- kbd == true &amp;amp;&amp;amp; code == true&lt;br /&gt;
			ret = (&#039;&amp;lt;%s&amp;gt;%s&amp;lt;/%s&amp;gt;&#039;):format(tagName, ret, tagName)&lt;br /&gt;
		end&lt;br /&gt;
		tagName = &#039;code&#039;&lt;br /&gt;
		if plaincode then&lt;br /&gt;
			css = {&lt;br /&gt;
				background = &#039;transparent&#039;,&lt;br /&gt;
				border     = &#039;none&#039;,&lt;br /&gt;
				color      = &#039;var(--color-emphasized, #101418)&#039;,&lt;br /&gt;
			}&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if show_result then&lt;br /&gt;
		local result = mw.getCurrentFrame():expandTemplate{title = addTemplate(args[1]), args = codeArguments}&lt;br /&gt;
		ret = ret .. &amp;quot; → &amp;quot; .. result&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if expand then&lt;br /&gt;
		local query = mw.text.encode(&#039;{{&#039; .. addTemplate(args[1]) .. string.gsub(codeArgumentsString, textPartBuffer, &amp;quot;|&amp;quot;) .. &#039;}}&#039;)&lt;br /&gt;
		local url = mw.uri.fullUrl(&#039;special:ExpandTemplates&#039;, &#039;wpInput=&#039; .. query)&lt;br /&gt;
		mw.log()&lt;br /&gt;
		ret = ret .. &amp;quot; [&amp;quot; .. tostring(url) .. &amp;quot;]&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if yesno(args.nowrap) then&lt;br /&gt;
		templateStyles.nowrap = true&lt;br /&gt;
		table.insert(classes, &#039;nowrap&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if tagName or #classes &amp;gt; 0 or css then&lt;br /&gt;
		local span = mw.html.create(tagName or &#039;span&#039;)&lt;br /&gt;
			:addClass(table.concat(classes, &#039; &#039;))&lt;br /&gt;
			:wikitext(ret)&lt;br /&gt;
&lt;br /&gt;
		if css then&lt;br /&gt;
			span:css(css)&lt;br /&gt;
		end&lt;br /&gt;
		ret = tostring(span:allDone())&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local ts = {}&lt;br /&gt;
	if templateStyles.mono then&lt;br /&gt;
		table.insert(ts, mw.getCurrentFrame():extensionTag{&lt;br /&gt;
			name = &#039;templatestyles&#039;,&lt;br /&gt;
			args = { src = &#039;Template:Mono/styles.css&#039; }&lt;br /&gt;
		})&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if templateStyles.nowrap then&lt;br /&gt;
		table.insert(ts, mw.getCurrentFrame():extensionTag{&lt;br /&gt;
			name = &#039;templatestyles&#039;,&lt;br /&gt;
			args = { src = &#039;Template:Nowrap/styles.css&#039; }&lt;br /&gt;
		})&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if yesno(args.debug) then&lt;br /&gt;
		ret = ret .. &#039;\n&amp;lt;pre&amp;gt;&#039; .. mw.text.encode(mw.dumpObject(args)) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return table.concat(ts) .. ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Li&amp;diff=1537</id>
		<title>Li</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Li&amp;diff=1537"/>
		<updated>2026-08-17T22:07:18Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT {{Localized link}}&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
	<entry>
		<id>https://labwiki.maceys.net/index.php?title=Li&amp;diff=1536</id>
		<title>Li</title>
		<link rel="alternate" type="text/html" href="https://labwiki.maceys.net/index.php?title=Li&amp;diff=1536"/>
		<updated>2026-08-17T22:07:00Z</updated>

		<summary type="html">&lt;p&gt;Lynn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT {{Template:Localized link}}&lt;/div&gt;</summary>
		<author><name>Lynn</name></author>
	</entry>
</feed>