Self-host a Gen-AI powered Wordpress site using Kubernetes on any commodity hardware

Arko Basu
20 min readMay 26, 2024

--

Now why on earth would anyone want to self-host a Wordpress site? That too on commodity hardware, and on top of that using Kubernetes (often abbreviated as K8s)! Surely I must be joking right? Well, this article is to hopefully convince you (and to some extent my inner self) otherwise. Also I am a huge proponent of using open-source technologies and cutting costs wherever possible. And these 2 open-source projects (Wordpress and K8s) hold a special place in my floodlight consciousness.

Players in the field

WordPress is the ultimate playground for digital dreamers and website wizards alike. Born in 2003, it emerged as a humble blogging platform but soon blossomed into a powerhouse content management system (CMS) that fuels millions of websites worldwide. What makes WordPress truly magical is its community-driven spirit. With a vast ecosystem of developers, designers, and enthusiasts collaborating, tinkering, and sharing their creations, WordPress evolves constantly, offering endless possibilities for crafting stunning websites, generative AI powered blogs, e-commerce stores, and so much more without much coding experience.

Take a gander at this 2024 Forbes article that outlines the bare minimum costs for a simple Wordpress site though. You’d think a couple hundred US dollars ain’t so bad to get started with? But it’s only the first year in most cases. The costs steeply rise once your promotional offers expire at the end of the first year with most vendors. The guys at WebFX does a much better justice in analyzing the true costs for a Wordpress site on this article.

Now whether you’re a tech-savvy individual tinkering with the latest innovations, a growing cost-conscious organization, an educational institution, or a professional seeking custom, scalable web solutions, Kubernetes could be your new best friend. Think of K8s as your superhero team for managing your applications (in this article’s case a Wordpress site). It can seemingly be complex from the outside, but its really not once you get to know it a bit and it packs a powerful punch!

Kubernetes originated from Google’s internal project called Borg, a sophisticated system for managing containers. In 2014, Google open-sourced Kubernetes under the Cloud Native Computing Foundation (CNCF), making its advanced container orchestration capabilities available to the broader tech community. Since then, Kubernetes has become a leading solution for managing containerized applications, known for its scalability and robust features.

This IBM Review puts costs as the number one driver for K8s adoption in the industry. The Kubernetes Case-Studies page is full of real world consumer use cases that will tell you how companies have saved operational costs, reduced time-to-market, and increased performance and hence their business just by Kubernetes adoption alone.

Inspiration

So why self-hosted? If you’re looking for a cost-effective, scalable solution for the long term that’s cloud-native but doesn’t need a big upfront or a monthly investment, this article is for you. The idea of self-hosting WordPress on Kubernetes comes from wanting total control, efficiency, and scalability in web hosting without having to ramp up on cloud native services.

It also aims to create a cloud-agnostic solution that can easily move to any cloud if needed without too much refactoring of deployments strategies or custom source code. Imagine being like Nick Fury, directing every aspect of the Avengers’ team. It might feel overwhelming at first, but once you understand the ecosystem, Kubernetes can help you Avenge your application’s performance and scaling issues in a way that will leave you in awe.

With self-hosted K8s, you will not just be hosting WordPress; you will be creating a flexible experience that handles traffic spikes and growth effortlessly, without worrying about cloud native services or costs.

Goal

In this article we will explore deploying a self-hosted generative AI powered Wordpress site on Kubernetes and expose it to the internet with minimal effort and costs.

High level steps:

  1. Getting Started with setting up a system where we can perform this deployment.
  2. Deploy a Kubernetes certified and conformant cluster.
  3. Deploy Wordpress with Persistent Storage on a Kubernetes cluster.
  4. Securely expose the Wordpress deployment to the internet using CloudFlare Tunnel.
  5. A brief introduction to generative AI powered websites.

Note: This is not a production grade deployment or a recommendation for one. This is only meant to get you introduced and get started. Will look forward to write a separate article about low-cost High Availability (HA) Production grade deployment strategy on-prem/bare-metal if there is enough interests.

Step 1: Getting Started

Let’s first find a system where we can do this. We will be using Canonical products that work on most Linux Distributions using Snap Store. This system could be anything like:

  • A Pi like Single Board Computer (SBCs)
  • A NUC/Mini-PC
  • An old Desktop that you are comfortable completely re-imaging with a Ubuntu/Debian distribution (last 3 stable versions only) or even using VirtualBox in.
  • Any cloud VM if you would like to use some free-tier service for taking this on a spin (not recommended — the minimal costs for running a tiny VM or even a managed K8s cluster can be substantial per month).
  • Your local computer (not recommended).

I have a handful of SBCs at home and I will be using one of those for this article. But the steps should overall work for any linux based system that is supported by Canonical’s — Snap.

Boot up the VM/Virtual Box instance/the physical bare-metal system with a more recent Ubuntu or Debian Server/Desktop version and log into it. In case you don’t need or care for a desktop experience and only wish to deploy a Wordpress site on this machine — a server image is highly recommended for commodity hardware in favor of performance trade-offs. All we will be doing are running commands on a terminal so a server version of the OS should be just fine.

Bare-Metal system CPU information

Note: I am using a self-compiled Debian ISO as explained in this article, but you can use whatever image you’d like as long as it’s a Linux distribution that supports snap or where microk8s is installable.

The system I am using costs less than 100 US dollars to build. Take a look at this doc if you want to grab the parts and build one yourself.

Step 2: Deploy a K8s Cluster

Now there are many ways of deploying a K8s cluster. Just take a look at the number of solutions and providers there are in the market. I have used about a good 40-ish percent of these solutions across multiple cloud environments, and by far Microk8s from Canonical is my personal favorite. And that’s why my earlier recommendation of using an Ubuntu or a Debian based system. Microk8s is available on the Snap-store which makes it super easy to deploy on a large variety of Linux Distributions.

Deploying a K8s using Microk8s in a standalone cluster (without High-Availability) is as simple as running these following commands:

# Update the system
sudo apt update

# Install snapd daemon
# Refer: https://snapcraft.io/docs/installing-snapd
# To deploy in your OS choice
sudo apt install snapd

# Install snap core to get the latest version
sudo snap install core

# Deploy a CNCF Certified Kubernetes cluster
# Refer: https://microk8s.io/docs/getting-started
# For more details for a standalone deployment
sudo snap install microk8s --classic --channel=1.30

# Join the group
sudo usermod -a -G microk8s $USER
mkdir -p ~/.kube
chmod 0700 ~/.kube

# Re-enter session for permissions to refresh.
su - $USER

# Check deployed Kubernetes cluster status
microk8s status --wait-ready
Deploying CNCF Conformant K8s Cluster

Note: Microk8s shows that it’s not in High Availability mode since this cluster is made of only one node. But that’s okay. This is just for testing purposes.

Feel free to check for pods and deployments:

K8s cluster default resources

This is what you can call a cluster made of a private kubernetes node since the device itself has no external IP that is reachable from the Internet. You can likely compare this to a machine within a private subnet in a Virtual private cloud environment on any cloud provider.

All Microk8s deployments have the right kubectl version packaged within it’s deployment, so you don’t really need to install kubectl to manage or diagnose your cluster. You can do something as simple as make an alias by running:


alias kubectl="microk8s kubectl"

# OR/AND

alias k8s="microk8s kubectl"
Using microk8s packaged kubectl with alias

And with those few commands you now have a fully conformant Kubernetes cluster running all in less than 2 minutes. You’d be surprised how dramatic that difference is if you get around to using some cloud native tools to do a cluster deployment on any provider. Let’s go on with our next step.

Step 3: Deploy Wordpress with Persistent Storage

What is Kubernetes Persistent Storage?

K8s offers self-healing capabilities for your containerized applications by restarting or rescheduling containers as needed as long as a node is available with the right scheduling conditions. You can refer to this beautiful article for a more in-depth review on how to build fault tolerant architectures on Kubernetes and how K8s orchestrates everything under the hood.

However, since in this article we are only dealing with a single node cluster there is no HA and there is no fault tolerance if the node itself goes down/offline. Hence this is not recommended for production scenarios. But I’m still going to touch on this topic to explain why Persistent Storage is needed.

Kubernetes persistent storage allows applications running in a k8s cluster to retain data across pod restarts and rescheduling (even across nodes). Unlike ephemeral storage, which is tied to the lifecycle of a pod, persistent storage ensures data durability and availability. This is achieved through Persistent Volumes (PVs) and Persistent Volume Claims (PVCs). PVs represent a piece of storage in the cluster that has been provisioned by an administrator (us in this case) or dynamically by K8s through means allowed by an administrator, while PVCs are requests for storage by users. This abstraction enables seamless storage management and scaling for stateful applications within Kubernetes.

Now Wordpress is a stateful application. We need to provide a persistent storage for both the RDBMs (MariaDB in this case) and the Wordpress application to use.

To simplify managing these we are going to use Kubernetes’ Dynamic volume provisioning capability with one of it’s many storage providers. For our use case — we want to use something that can easily port to the cloud. We wouldn’t want to worry too much about the storage layer and let one of Kubernetes storage providers do all the heavy lifting. If you see my medium profile you will know how big of a Ceph fan I am. Of all the open source cloud agnostic storage solutions for Kubernetes, Ceph is by far the best in my opinion. And we shall soon see how easy it is to setup.

Note: We are not going to discuss Ceph in much detail. Please look at my other articles for a more in-depth review for Ceph. We are using Ceph since we can build on top of this article and grow the Wordpress site into a high availability architecture without much administrative overhead for when the time comes.

So let’s get started. Setting up Ceph Cluster and connecting it to Kubernetes can be done by running the following commands:

# Install Ceph using another canonical product
# Refer: https://canonical-microceph.readthedocs-hosted.com/en/reef-stable/tutorial/single-node/
# For more details and step by step guide
sudo snap install microceph --channel=latest/edge

# Let's freeze this package. Upgrading it can have bad consequences without an ugrade strategy
sudo snap refresh --hold microceph

# Bootstrap the ceph cluster
sudo microceph cluster bootstrap

# Check the status
# This command shows the full cluster status
sudo microceph.ceph status
# This command shows a summary
sudo microceph status

# Lets' add some file backed Virtual Disk space (aka Ceph OSDs)
# to our single node ceph cluster. This will utilize default primary storage
# where our OS is installed.
# Each loop device of 15GB size totalling 45 Gigs. enough for a test deployment
sudo microceph disk add loop,15G,3

# Check the status and list the disks
sudo microceph.ceph status
sudo microceph disk list

# Now customize the cluster for a single node deployment
sudo microceph.ceph config set global osd_pool_default_size 2
sudo microceph.ceph config set mgr mgr_standby_modules false
sudo microceph.ceph config set osd osd_crush_chooseleaf_type 0

# Check if the RBD kernel is available.
# Without this your Storage provisioning will not work
sudo modprobe rbd

# Now let's deploy the Rook addon for Ceph into our Kubernetes cluster
sudo microk8s enable rook-ceph

# Let's connect the ceph Cluster to our Kubernetes cluster
# Since the cluster is on the same node we don't need to pass details
# Microk8s is smart enough to recognize a ceph cluster on the same node.
sudo microk8s connect-external-ceph

# Check for the ceph cluster status within the kubernetes cluster by running
k8s --namespace rook-ceph-external get cephcluster

# At this point you should have a fully connected Ceph Cluster in your K8s cluster
# Check for provisioned storage classes
k8s get sc

# Let's go ahead and set it as the default
k8s patch storageclass ceph-rbd -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# Validate patch
k8s get sc

# Validate all pods
k8s get po -A
Images showing all the above setps for connecting a single node ceph cluster to your Kubernetes cluster

We are done setting up the stage. So let’s get to the Wordpress deployment finally, shall we? This can be done by running the following commands:

# Like kubectl microk8s comes packed with helm by default as well. 
# So lets just use that
alias helm="microk8s helm"

# validate helm binary
helm version

# We are going to use Bitnami charts.
# Please refer to this for more information: https://github.com/bitnami/charts/tree/main/bitnami/wordpress/

# Let's create YAML file that represent our Helm deployment overrides
# Please update the values as necessary
# Please refer to the charts document for more customization and information about all options being used.
# For example I can configure Cache Plugins
# directly as part of the helm deployment
# The secrets are not the actual passwords. Just the K8s secret names that we will
# be creating to store the actual credentials securely.
cat <<EOF > wordpress-testing.yaml
allowOverrideNone: true
wordpressUsername: wp-admin
existingSecret: "wp-admin-pw"
wordpressEmail: contactme@arkobasu.space
wordpressFirstName: Arko
wordpressLastName: Basu
wordpressBlogName: Arko's Gen-AI powered blog!
wordpressConfigureCache: true
memcached:
enabled: true
resourcesPreset: "micro"
resourcesPreset: "small"
mariadb:
primary:
resourcesPreset: "micro"
EOF

# Let's create secrets for the wordpress helm deployment to consume
# As we have specified in the above file
# Secret 1: wp-admin
# this is the Website admin console login credential. Should be kept very secure
# Please refer to the chart for more information
k8s create secret generic wp-admin-pw \
--from-literal=wordpress-password='S!B\*d$zDsb='

# Let's deploy the Wordpress helm chart
export REGISTRY_NAME=registry-1.docker.io
export REPOSITORY_NAME=bitnamicharts
helm install wp-testing-release \
--set mariadb.auth.rootPassword=secretPassword -f wordpress-testing.yaml \
oci://$REGISTRY_NAME/$REPOSITORY_NAME/wordpress
Images showing above steps

Give it a few minutes. Now let’s validate the deployment.

# Check the pods and deployments
k8s get po

# Check the PV and PVs
k8s get pv,pvc
Image showing healthy deployments with their persistent storage backed by rook-ceph dynamically provisioned

If everything shows as “Running” you are good! Which means we have a successfully deployed Wordpress site. Let’s check the service:

k8s get svc
External IP for wordpress-testing-release’s Service

The service doesn’t have an external IP, and it shows as “pending”. The reason for this is — Kubernetes does not offer an implementation of network load balancers (Service type — Load Balancers) for bare-metal clusters.

“The implementations of network load balancers that Kubernetes does ship with are all glue code that calls out to various IaaS platforms (GCP, AWS, Azure…). If you’re not running on a supported IaaS platform (GCP, AWS, Azure…), LoadBalancers will remain in the “pending” state indefinitely when created.” — MetalLb.

I am doing this in a home-lab within a private network as we discussed earlier. So no exposing services through an External Load Balancer. We could use something like MetalLb that provides the capability for load balancing on bare-metal systems. But we don’t really need that since it’s not a HA deployment. We only have one node and one pod for our deployments so no load balancing required. And even if we used MetalLb, we still won’t be able to expose it since this system is within a private subnet within my home network. I could use my home router’s main public IP in MetalLB configuration to expose the app to internet. But that opens the door to exposing all my home devices to the world. Without any prior network segmentation that is. A big no-no for me. This is where we come to our final step. Cloudflare to the rescue with a clean solution that is super safe and secure.

Step 4: Expose the Wordpress site using Cloudflare tunnel

What is Cloudflare Tunnel?

Cloudflare Tunnel is a Cloudflare Zero Trust product that aims to replace legacy security perimeters within their global network.

Image lifted from Cloudflare documentation

It’s a beautiful and cutting edge technology that allows a secure way to connect resources to Cloudflare’s global network without having to use a publicly routable IP address. Which works perfectly for our use case.

What you will need to get started?

You will need a domain for this. If you already have one, simply setup Cloudflare following the instructions here. If you don’t have a domain — buy one from Cloudflare. There are many cheap ones for less than 10 US dollars a year. You can go with a cheaper registrar like Namecheap where you could likely find domains for less than 2 US Dollars, but trust me when I say this — once you use CloudFlare you won’t want to use anything else. It even has a terraform provider and is extremely customizable and automation friendly.

So let’s get started.

For the initial setup of tunnel creation I would recommend doing this on your personal computer. Follow the download and install instructions based on your OS. I am doing this on a MacOS.

# Refer for more detailed information: https://developers.cloudflare.com/cloudflare-one/tutorials/many-cfd-one-tunnel/
# Install cloudflared CLI
brew install cloudflared

# Login to Cloudflared
cloudflared tunnel login

# Create a tunnel
cloudflared tunnel create wp-testing-release

# Create a DNS record for routing traffic
# In this case the I am using a CNAME.root-dns-record pattern
cloudflared tunnel route dns wp-testing-release wp-testing.arkobasu.space

# Now ship the generate file to the SBC/System where the cluster is running
scp ~/.cloudflared/<generated-tunnel-id>.json <username>@<ip-address>:/home/<username>/.tunnel_credentials.json
Moving the generated CloudFlare tunnel credentials to our cluster node

You can go to the CloudFlare dashboard and check for the newly created DNS record.

# Now let's save the tunnels credentials as a secret in the cluster
k8s create secret generic tunnel-credentials \
--from-file=credentials.json=<full-path-of-file>
Tunnel credentials saved in K8s cluster

Next step is to define 2 k8s resources: a deployment and a configmap for our Cloudflared Tunnel. Simply copy and dump the contents of this file into a file named cloudflared.yamlon the machine running the K8s cluster and make any necessary changes. The primary changes you’d need to make are:

  1. (Optional) Change the image tag to latest
    The official doc’s image uses a legacy image that doesn’t support ARM64 architectures. Without this the deployment will fail if you are doing this on a Arm64 system.
  2. The secret in case you changed the secret name for the tunnel created earlier.
  3. (Optional) Change the tunnel name if you’d like.
  4. The hostname. This is what we set earlier for routing traffic with a CNAME.<root-dns-record>pattern.
  5. The service within the configMap definition as per your actual deployment service name.
    This will be the same as your service name for the Wordpress deployment which you can retrieve by running the following command:
k8s get service <service-name> -o jsonpath='{.metadata.name}'

Once completed making necessary changes go ahead and deploy it by running the following command:

k8s apply -f cloudflared.yaml 
Image showing Cloudflared tunnel deployment. After making necessary changes
Registered Tunnel

At this time you should have everything setup. Simply head on to a browser and type in the DNS record you used for setting up the tunnel.

SSL protected Global DNS routing for your Wordpress site with minimal setup.

Congratulations. We have successfully deployed a Wordpress site that already has a sample page ready and setup. Simply login to the admin portal by navigating to: https://CNAME.root-dns-record/adminand start customizing the website as you need.

Logging into the admin portal to get started with customizing the Wordpress site

Feel free to go ahead and change themes and customize the default landing page. There are extensive catalog of very cool drag and drop themes for building anything you can imagine: from eCommerce stores to blogs, to Portfolios and so much more. Here are some of the things you can try out:

  • Go through W3 Total Cache Setup from the “Performance” option on the navigation menu.
  • Install a cool new theme you like and see the landing page in real time.
Search for cool themes
Install and activate a new theme
  • Activate some useful plugins. Things like SEO Optimizers, Google Analytics for site traffic, Migration tools (especially if you are using this as your development system), SMTP mail so that your Wordpress site can send emails and manage notifications, etc.
Wordpress plugin dashboard

Before we proceed to the final step of empowering this blog with generative AI, let’s check a few things out first:

# Let's check the Ceph Cluster's health
alphaduriendur@hc-opi3b8-1:~$ sudo microceph.ceph status
cluster:
id: 63c90b28-bac8-457c-a39b-d89b31787dc7
health: HEALTH_OK # Showing health OK

services:
mon: 1 daemons, quorum hc-opi3b8-1.arkobasu.space (age 4h)
mgr: hc-opi3b8-1.arkobasu.space(active, since 4h)
osd: 3 osds: 3 up (since 3h), 3 in (since 3h)

data:
pools: 2 pools, 33 pgs
objects: 110 objects, 273 MiB # Good Ceph Memory usage
usage: 588 MiB used, 44 GiB / 45 GiB avail
pgs: 33 active+clean

# Let's now check CPU load average. If the
# value is less than the number of cores on the system
# then we are good.
alphaduriendur@hc-opi3b8-1:~$ uptime
01:02:26 up 7:12, 1 user, load average: 0.98, 1.34, 1.41
# Since the Orange Pi 3B (where I am testing this) has 4 CPU Cores
# this is very good
alphaduriendur@hc-opi3b8-1:~$

# Let's enable metrics server to check our K8s cluster utilization
alphaduriendur@hc-opi3b8-1:~$ microk8s enable metrics-server
Infer repository core for addon metrics-server
Enabling Metrics-Server
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
clusterrolebinding.rbac.authorization.k8s.io/microk8s-admin created
[sudo] password for alphaduriendur:
Metrics-Server is enabled
alphaduriendur@hc-opi3b8-1:~$

# Now let's check K8s cluster utilization
alphaduriendur@hc-opi3b8-1:~$ k8s top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
hc-opi3b8-1.arkobasu.space 1025m 25% 3102Mi 40%
alphaduriendur@hc-opi3b8-1:~$ k8s top pods
NAME CPU(cores) MEMORY(bytes)
cloudflared-854b46c94f-2v698 8m 16Mi
cloudflared-854b46c94f-4pcfw 7m 16Mi
wp-testing-release-mariadb-0 11m 132Mi
wp-testing-release-memcached-7f6c8b9c68-ljrvv 5m 6Mi
wp-testing-release-wordpress-65c4f5475b-9qhkp 19m 197Mi
alphaduriendur@hc-opi3b8-1:~$
Showing post install overall health of the cluster

As you can see we have a very decent performance. The system is at 40 percent memory and 25 percent CPU utilization. Which tells me that should this deployment need we can still vertically scale up the deployment within this single node cluster itself.

Pretty cool when you consider that this is all for less than 110 bucks. Inclusive of the device and the cost of running it on a low energy footprint at 15W max for 24 hours 365 days — costing less than 16 dollars a year. Its nothing compared to any cloud solution which would cost you more than this in a single month on a single node cluster. Take for example managed Kubernetes distributions. Except for Azure — each provider has a hourly cost for just the control plane (without any worker node) that will alone costs upwards of 70 USD dollars every month. Or even a simple Wordpress hosting provider (without Kubernetes) would cost you a good 50–75 dollars a month for all that we covered here for next to no costs.

Step 5: Empowering Wordpress sites with Generative AI

In the wake of all the innovation around Generative AI and using Large Language models, people like myself have been greatly fascinated by how we can leverage this technology very easily without having to learn much coding and/or machine learning based application development.

Wordpress and Kubernetes are two of the top open-source communities that have seen some amazing adoption of Generative AI based tooling to bring this fascinating capability to a more wider audience. From content like blog and image generation to SEO optimization using AI, and now even UI generation — these tools are only getting better and cheaper.

My hope in this section is to get you introduced to some amazing work that is being done in the Wordpress community for bringing generative AI tooling to Web developers and designers alike through Plugins. Some of them that we are going to cover are:

AI Power Plugin

This is a Plugin developed specifically for use with Chat-GPT. With this you can add ChatGPT directly to your website as a conversation bot, automatically create blogposts on any topic, generated images for your blogposts or eCommerce advertisement campaigns using Dall-E, generate dynamic forms and so much more. It does require an API Key from OpenAI. But a free account with free basic usage limits should be enough to get you started. Recommend using the 3.5 model variant wherever possible. If you can tweak the prompts right, you can get some amazing results.

  • Elementor and/or Elementor Pro
    The Pro version has an AI enabled website builder which has been performing pretty well in my opinion it can inject blocks with creative texts and images. I personally am not an website designer — but given the tools and how easy it is to use with a no-code environment, all driven my prompts is something fascinating to see.
Elementor Plugin

Conclusion

With all this, we come to the end of the article. I hope you’ve enjoyed reading it as much as I’ve enjoyed writing it. Truth be told,in hindsight looking back as we reach the end of the article, I would think my inner self would need to expand this article to a High Availability architecture to garner some more convincing. But just the sheer potential of cost savings inspires me to do so especially since the expense for ramping up in deploying a fully functional K8s + Wordpress stack (as you saw from this article) will always be substantially lower on bare-metal self managed systems when you consider the costs for learning a cloud native services.

I look forward to your feedback on this approach to building a low-cost DIY solution for self-hosting an experimental, developmental website using Kubernetes and WordPress. Your thoughts and experiences are invaluable, and I can’t wait to hear what you think about this method. Thank you all for indulging me this read if you got to the end of it! Cheers!

--

--