Categories
Me

Emotive Amiga Code

In this post, I respond to July 2023’s T-SQL Tuesday #164 Invitation and talk about emotive code on the Amiga.

tsql tuesday

Table of Contents

Introduction

This month’s T-SQL Tuesday comes from Erik Darling. To paraphrase his invitation:

Think back to the last time you saw code that made you feel a thing. Hopefully a positive thing. Think along the lines of: surprise, awe, inspiration, excitement. Or maybe it was just code that sort of sunk its teeth into you and made you want to learn a whole lot more.

Now, while Erik’s invitation mentions the last time, I want to talk about the first time. This is an ideal opportunity to expand on something I wrote on my About page about AMOS tutorials. But before I start explaining why I consider some mid-nineties Amiga code to be emotive, I should explain a few things.

Key Topics

This section introduces some of the key topics I talk about in this post.

Amiga

The Amiga home computer was first released by Commodore in 1985, and was one of the first computers with custom graphics and sound hardware.

Amiga500 system
An Amiga 500

The Amiga was designed to compete with the Apple Macintosh and the IBM PC. It had a custom chipset and a multitasking operating system. These made the Amiga appeal to a wide range of audiences including:

The Amiga also had expansion capabilities, with various third-party add-ons available like the Video Toaster production tool. This allowed the Amiga to be used for visual effects in TV shows like Babylon 5 and SeaQuest DSV, and films like Apollo 13 and Titanic.

The Amiga Wikipedia page has information on history, chipsets, trivia and more.

Amiga Format

Amiga Format was a monthly UK computer magazine published from 1989 to 2000.

logo amiga format

Broadly, there were three types of Amiga magazines:

  • Game-focused magazines like Amiga Power (Yayyy – Ed).
  • Serious magazines like Amiga Shopper.
  • Magazines with some of both, like Amiga Format.

Amiga Format was about 50% games to 50% serious. This expanded to their coverdisks, with a fairly even split of utilities and game demos across each month’s disks.

My first Amiga Format was issue 30, purchased alongside the family Amiga 500+ Cartoon Classics bundle:

AmigaFormat iss30

And I still have them both! Young me chucked the magazine around a fair bit, so years later I bought a copy from eBay in better condition. I’ll get some photos on Instagram when I get a chance.

The Amiga Format Wikipedia page has some additional information including some regular features.

AMOS

AMOS (or to give its full name AMOS BASIC) is an Amiga dialect of the BASIC game programming language.

AMOSBox

First published in 1990 by Europress Software, AMOS was well received and sold over 40,000 copies worldwide.

Interaction comes via the AMOS Editor: a text editor with the AMOS language built into it. This allows users to type, edit, save and run AMOS programs from within the editor, without the need for a separate runtime tool or compiler.

amos

The AMOS Wikipedia page goes into more technical detail, and this PowerPrograms page has a user’s AMOS perspective.

In addition, AMOS Professional has a GitHub repo, and I would be remiss if I didn’t include Dan Wood’s excellent AMOS retrospective video:

From AMOS To YAML

In this section, I talk about the Amiga code that ended up being so emotive and indirectly set my future career in motion.

Scene Setting

It’s Christmas 1992, and I’m a young shark pup still in single digits. I hit the newsagent’s magazine shelves with pocket money in hand and buy that month’s Amiga Format:

AmigaFormat iss42

Yes – it does say January 1993 on the cover. Before writing this post I never had a good explanation for this, so I reached out to the Amiga Addict Discord for some clarity.

It turns out that, during this period, Amiga Format produced issues every four weeks instead of monthly. Former Amiga Format editor Marcus Dyson speaks about this at around 38:30 in Maximum Power Up podcast episode 139.

But anyway.

Throughout 1992, Amiga Format’s coverdisks featured a series of fully-functional software packages known as The Amiga Format Collection. This collection included:

  • Fractal landscape generator Vista (AF33)
  • HAM paint and animation package Spectracolor Jr (AF35)
  • Machine code assembler Devpac 2 (AF39)

Adorning Amiga Format 42’s cover was AMOS:

af42 1993 01 d1

I’d read about AMOS before. Amiga Format issue 35 had a demo of Easy AMOS, and even the games-centric Amiga Power mentioned AMOS in its Public Domain section. And now, suddenly every Amiga Format buyer had an unrestricted copy of it.

Amiga Format partnered this with a Learning AMOS magazine section. Basic principles, syntax and screenshots were introduced across nine pages, with the tenth page having a tutorial for a scrolling text demo. This was to be the first part of a monthly Mastering AMOS series.

What A Pong

Now, young me didn’t pay much attention to this part of issue 42. It was Christmas after all, and I had demos of Lemmings 2, Fire & Ice and Bill’s Tomato Game to keep me busy!

Enter Amiga Format issue 43. This month’s AMOS tutorial was Solo Pong – a simple version of Pong with the player controlling both bats. This was deliberate – the focus here was on learning AMOS as opposed to learning coding.

Young me was intrigued! The scrolling demo didn’t really appeal, but this was a game! The complete script was only half a page long, and the tutorial explained each line. So I gave it a shot, and within an afternoon I had my very own Pong clone!

2023 07 07 solopong

The code itself made no sense to me – I was copying words from a magazine onto a screen and hoping for the best. But I found the process itself captivating. The idea that all the software I’d run on my Amiga was made by words on a screen – and words that I could type myself at that – was a revelation!

On an unrelated note, Amiga Format 43 included the last of the Amiga Format Collection series – the database package Prodata. So not only did issue 43 introduce me to coding – it also foreshadowed my eventual tech career!

Mastering AMOS Legacy

Amiga Format continued the Mastering AMOS series until issue 51. They went on to include AMOS Professional with issue 67, and ran an Ultimate AMOS tutorial series from issue 68 to issue 73.

Damien Junior didn’t do much more with the Mastering AMOS series after Amiga Format 43. I vaguely remember having a go at the Amiga Format 44 tutorial, which added some bells and whistles to the existing Pong code. But after that, it started going over my head and I lost interest. Although to be fair, I was a child!

The coding curiosity remained. As the 1990s went by I found myself trying out Amiga Format’s DevPac 2 and Blitz Basic tutorials, and by the end of the 1990s I was learning Excel formulae and VBScript at school. During the 2000s I started using HTML, CSS and PHP, and began using T-SQL, Powershell and DAX at work in the 2010s.

Which brings me to the present day, where I find myself learning Python and experimenting with reStructuredText (for Read The Docs) and YAML (for CloudFormation). And I thought, after thirty years, why not revisit the Amiga Format tutorial and see how I feel about AMOS now?

Thirty Years Later

Like many self-respecting Amiga fans I have WinUAE installed on my laptop, and an ADF of the AMOS coverdisk isn’t hard to find. So I fired everything up! And, after thirty minutes of typing, squinting and error troubleshooting, witness the result of my mighty endeavour – SHARK PONG:

2023 07 07 sharkpong

I clearly wasn’t squinting hard enough though. In this script, AMOS uses the ball’s X coordinate to determine if a player has lost. Where the tutorial’s LOSE conditions are:

If X>=320 Then LOSE : Goto RESTART
If X<=-20 Then LOSE : Goto RESTART

I missed the minus in the second IF statement and wrote:

If X<=20 Then LOSE : Goto RESTART

Thus creating a version of Shark Pong that Left Paddle can never win, as the losing X coordinate is in front of the paddle:

SharkPongFail

But then again, it wouldn’t be an early 90s tutorial follow-along without an unintended and slightly hilarious bug, right? And the command to stop running the code? Control-C. That broke my brain briefly.

But yeah. Revisiting the same code that captivated me 30 years ago was a blast! The original tutorial code is in my GitHub for those curious to see it, both as an unformatted text file and in glorious BlitzBasic!

Further Reading

Summary

In this post, I responded to July 2023’s T-SQL Tuesday #164 Invitation and talked about emotive code on the Amiga.

I had a lot of fun writing this! A phrase like ’emotive Amiga code’ might not sound fascinating on paper, but it was interesting to revisit that tutorial after so long. I even finally answered a question that’s lingered in my head for a few decades.

Thanks to Erik for this month’s topic! My previous T-SQL Tuesday posts are here. If this post has been useful, please feel free to follow me on the following platforms for future updates:

Thanks for reading ~~^~~

Categories
Architecture & Resilience

Automating Application Management With Winget

In this post, I try automating my laptop’s application management with the Windows Package Manager tool Winget.

Table of Contents

Introduction

After much frustration with my laptop’s performance, I finally booked it in for upgrades to an SSD hard drive and 16GB RAM. It’s now very responsive and far faster!

The shop originally planned to clone my existing HDD drive onto the new SSD. Unfortunately, the clone kept failing due to some bad sectors. Fortunately, this didn’t present a risk of data loss – most of my files are in OneDrive, and everything else is either in Amazon S3 or on external drives.

The failing clone meant that none of my previously installed programs and packages were on the new drive. I wasn’t flying blind here though, as I regularly use the free Belarc Advisor tool to create a list of installed programs.

But this is a heavily manual process, and the Belarc Advisor files contain a lot of unnecessary data that isn’t easy to use. So I found myself looking for an alternative!

User Story

In this section, I outline the problem I want to solve.

I want to capture a list of all applications installed on a given Windows device so that I can audit my device and have a better disaster recovery strategy.

ACCEPTANCE CRITERIA:

The process must be fully automated. I don’t want another job to do – I want the device to own this process.

The process must be efficient. Belarc Advisor gets the job done, but it takes time to load and does a bunch of other stuff that I don’t need.

There is no budget. Belarc Advisor isn’t ideal, but it’s free. I don’t want to start spending money on this problem now.

Introducing Winget

This section explains what Winget is and examines some of the features and benefits it offers.

What Is Winget?

Winget is a Windows Package Manager that helps install, upgrade, configure and delete applications on Windows 10 and Windows 11.

Package Managers look through configured repositories like the Windows Package Manager Community Repository for applications. If the application is available, it will be downloaded from the repository and installed onto the device.

Microsoft has open-sourced Winget, and has committed it to their GitHub account. After installation, Winget is accessible via the Windows Terminal, PowerShell, and the Command Prompt.

Package Manager Benefits

Package Managers like Winget offer several benefits over traditional methods:

  • Applications are installed as CLI commands, so there is no need to navigate to different websites or go through multiple installation steps.
  • Their repositories enforce a strict submission policy and use standardized package formats, so applications are installed consistently and reliably.
  • They manage application dependencies. If a desired application needs another application to work, the package manager will automatically install that application as well.
  • They lend themselves well to CI/CD pipelines, IAC and disaster recovery, as package manager commands can be used in scripts and automated processes.
  • Community tools like winstall exist that can create batch-installation Winget commands and scripts using a web GUI.

Winget Commands

Winget regularly receives new commands, a list of which is maintained by Microsoft. These commands can be loosely grouped into:

For this post, I will be focusing on the last group.

winget list displays a list of installed applications. The list includes the current version and the package’s source, and has several filtration options.

The winget list syntax is:

winget list [[-q] \<query>] [\<options>]

winget export creates and exports a JSON file of apps to a specified path.

This JSON file can combine with the winget import command to allow the batch-installing of applications and the creation of build environments.

winget export‘s JSON files do not include applications that are unavailable in the Windows Package Manager Community Repository. In these cases, the export command will show a warning.

The winget export syntax is:

winget export [-o] <output> [<options>]

Winget Scripting With VSCode

In this section, I write a script that will run the Winget commands.

I’m writing the script using Visual Studio Code, as this allows me to write the Winget script in the same way as other PowerShell scripts I’ve written.

Unique Filename

Firstly, I want to give each file a unique filename to make sure nothing is overwritten. A good way to do that here is by capturing Get-Date‘s output formatted as the ISO 8601 standard:

$RunDate = Get-Date -Format 'yyyy-MM-dd-HHmm'

This returns a string with an appropriate level of granularity, as I’m not going to be running this script multiple times a minute:

2023-04-26-1345

Winget Export Code

Next, I’ll script my export command.

I need to tell Winget where to create the file, and what to call it. I create a new folder for the exports and capture its path in a $ExportsFilePath variable.

Then I create a $ExportsFileName variable for the first part of the export file’s name. It uses a WingetExport string and the device’s name, which PowerShell can access using $env:computername:

$ExportsFileName = 'WingetExport' + '-' + $env:computername + '-'

Including the computer’s name means I can run this script on different devices and know which export files belong to which device:

WingetExport-LAPTOP-IFIJ32T-

My third $ExportsOutput variable joins everything together to produce an acceptable string for winget export‘s output argument:

$ExportsOutput = $ExportsFilePath + '\' + $ExportsFileName  + $RunDate + '.json'

An example of which is:

C:\{PATH}\WingetExport-LAPTOP-IFIJ32T-2023-04-26-1345.json

Finally, I can script the full command. This command creates an export file at the desired location and includes application version numbers for accuracy and auditing:

winget export --output $ExportsOutput --include-versions

Here are some sample exports:

{
  "$schema": "https://aka.ms/winget-packages.schema.2.0.json",
  "CreationDate": "2023-04-27T11:02:04.321-00:00",
  "Sources": [
    {
      "Packages": [
        {
          "PackageIdentifier": "Git.Git",
          "Version": "2.40.0"
        },
        {
          "PackageIdentifier": "Anki.Anki",
          "Version": "2.1.61"
        },
        {
          "PackageIdentifier": "Microsoft.PowerToys",
          "Version": "0.69.1"
        }
      ],
      "SourceDetails": {
        "Argument": "https://cdn.winget.microsoft.com/cache",
        "Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
        "Name": "winget",
        "Type": "Microsoft.PreIndexed.Package"
      }
    }
  ],
  "WinGetVersion": "1.4.10173"
}

As a reminder, these exports don’t include applications that are unavailable in Winget. This means winget export alone doesn’t meet the user story requirements, so there is still work to do!

Winget List Code

Finally, I’ll script my list command. This is mostly similar to the export command and I create the file path in the same way:

$ListsOutput = $ListsFilePath + '\' + $ListsFileName + $RunDate + '.txt'

The filename is changed for accuracy, and the suffix is now TXT as no JSON is produced:

WingetList-LAPTOP-IFIJ32T-2023-04-25-2230.txt

Now, while winget list shows all applications on the device, it has no argument to save this list anywhere. For that, I need to pipe the winget list output to a PowerShell command that does create files – Out-File:

winget list | Out-File -FilePath $ListsOutput

Out-File writes the list to the $ListsOutput path, producing rows like these:

Name Id Version Available Source
Anki Anki.Anki 2.1.61 winget
Audacity 2.4.2 Audacity.Audacity 2.4.2 3.2.4 winget
DBeaver 23.0.2 dbeaver.dbeaver 23.0.2 winget
S3 Browser version 10.8.1 S3 Browser_is1 10.8.1

The entire script takes around 10 seconds to run in an open PowerShell session and produces no CPU spikes or memory load. The script is on my GitHub with redacted file paths.

Automation With Task Scheduler

In this section, I put Task Scheduler in charge of automating my application management Winget script.

What Is The Task Scheduler?

Task Scheduler began life on Windows 95 and is still used today by applications including Dropbox, Edge and OneDrive. Parts of it aren’t great. The Send Email and Display Message features are deprecated, and monitoring and error handling relies on creating additional tasks that are triggered by failure events.

However, it’s handy for running local scripts and has no dependencies as it’s built into Windows. It supports a variety of use cases which can be scripted or created in the GUI. Existing tasks are exportable as XML.

Creating A New Task

There is plentiful documentation for the Task Scheduler. The Microsoft Learn developer resources cover every inch of it, and these Windows Central and Windows Reports guides are great resources with extensive coverage.

In my case, I create a new ApplicationInventory task, set to trigger every time I log on to Windows:

2023 04 25 TaskSchedulerTrigger

The task starts powershell.exe, passing an argument of -file "C:\{PATH}\ApplicationInventory.ps1".

This works, but will force a PowerShell window to open every time the schedule runs. This can be stopped by configuring the task to Run whether user is logged on or not. Yup – it feels a bit hacky. But it works!

I now have a new scheduled task:

2023 04 25 TaskSchedulerNewTask

Testing

An important part of automating my application management with Winget is making sure everything works! In this section, I check the script and automation processes are working as expected.

I’ll start with the task automation. Task Scheduler has a History tab, which filters events from Event Viewer. Upon checking this tab, I can see the chain of events marking a successful execution:

2023 04 25 TaskSchedulerHistory

When I check the WingetExport folder, it contains an export file created on 25/04/2023 at 22:30:

2023 04 25 AppInventoryExports

And there are similar findings in the WingetList folder:

2023 04 25 AppInventoryLists

Both files open successfully and contain the expected data. Success!

Summary

In this post, I try automating my laptop’s application management with the Windows Package Manager tool Winget.

If this post has been useful, please feel free to follow me on the following platforms for future updates:

Thanks for reading ~~^~~

Categories
DevOps & Infrastructure

Migrating amazonwebshark To SiteGround

In this post, I examine the process of migrating amazonwebshark to SiteGround and give an overview of the processes involved.

Table of Contents

Introduction

When I started amazonwebshark I had to make some infrastructure decisions. I registered the domain name with Amazon Route 53, and then needed to choose a blog hosting platform.

In December 2021 I took advantage of a Bluehost offer and paid £31.90 for their Basic WordPress Hosting package. This included a variety of services including:

My Bluehost renewal came through earlier this month, priced at £107.76. I’ve had great service from Bluehost and have no complaints, but that price was quite a leap. So, before I accepted it, I decided to do some research and see what my alternatives were.

Hosting Alternatives

In this section, I go through the results of my research into alternative hosting platforms.

Amazon Lightsail

I started by looking at Amazon Lightsail. Essentially, Lightsail is a simplified way of deploying AWS services like EC2, EBS and Elastic Load Balancing.

Lightsail pricing differs from most AWS services. Instead of the common Pay-As-You-Go pricing model, Lightsail has set monthly pricing. For example, a Linux server with similar memory, processing and storage to my Bluehost server currently costs $3.50 a month.

There is an important difference though. While Bluehost has teams of people responsible for tasks like server maintenance, database recovery, hard disk failures and security patches, with Lightsail the infrastructure would become my responsibility. I would save money over Bluehost, but at the cost of doing my own systems admin.

And the above list isn’t even exhaustive! It doesn’t include the setup and maintenance of services like CDN, SSL certificates and email accounts, all of which come with their own extra requirements and costs.

At this point, Bluehost was still on top.

SiteGround

SiteGround is in the same business as Bluehost. It offers a variety of hosting solutions for an array of use cases and has good standing in the industry.

SiteGround also had a great Black Friday offer this year! It was offering pretty much the same deal as Bluehost at £1.99 a month:

2022 11 25 SitegroundOfferScroll

This is INSANELY cheap, especially considering how much all this infrastructure costs to run!

SiteGround has also developed a free WordPress plugin to automate migrations from other hosting platforms. While this isn’t unique to them, a combination of good reviews, extensive services, low hassle and a great price was more than enough to get me on board.

Migrate What Exactly?

Before continuing, I thought it best to go into a bit of detail about what exactly is being migrated. I’ve mentioned servers, databases and domains, but what gets moved where? And why?

Well, because I’m moving things around on the Internet, I need to talk about the Domain Name System (DNS).

Wait! Come back!

Explain DNS Like I’m 5

What follows is a very simple introduction to DNS. There’s far more to DNS than this, but that’s beyond the scope of this post.

Let’s say I want to phone The Shark Trust. I can’t type “The Shark Trust” into my handset – I need their phone number. So I open my phone book, turn to the S section and find The Shark Trust. Next to this entry is a phone number: 01752 672008. I type that number into the handset and get through to their office.

DNS is like the Internet’s phone book. Websites are held on servers, and the ‘phone numbers’ for those servers are IP addresses. When I request a website like amazonwebshark.com, my web browser needs to know the IP address for the server holding the site’s data, for example 34.91.95.18.

Explain DNS With Pictures

This WebDeasy diagram show DNS at a high level:

WebDeasy: How the Domain Name System (DNS) works – Basics

When a URL is entered into a web browser, a query is sent to a DNS server. Using the phone book analogy, the web browser is asking the DNS server for the amazonwebshark.com phone number.

DNS servers don’t have any IP addresses, but they know which ‘phone book’ to look in. These ‘phone books’ are called name servers. The DNS server finds and contacts the right name server, which matches the amazonwebshark.com domain name to an IP address.

The DNS server then returns this IP address to the web browser, which uses it to contact the server hosting the amazonwebshark.com resources.

In the diagram, the DNS-Server represents Route 53. Route 53 holds DNS records for the amazonwebshark.com domain name, and knows where to find the name servers that have the amazonwebshark.com IP address.

The webdeasy.de server represents the Bluehost name servers. These servers can answer a variety of DNS queries, and are considered the ground truth for initial site visits and browser caching.

amazonwebshark’s DNS Setup

At the start of December 2022 the amazonwebshark.com domain name was hosted by Route 53, with an NS record pointing at the Bluehost name servers:

2022 12 02 Route53Bluehost

The basic infrastructure looked like this, with outbound requests in blue and inbound responses in green:

2022 12 27 amazonwebsharkDNSdiagram

And that’s it! To further explore DNS core concepts, this DNSimple comic is well worth a read and this Fireship video gives a solid, if a little more technical, account:

Data Migration

In this section, I start migrating my amazonwebshark data from Bluehost to SiteGround.

SiteGround has an automated migrator plugin that copies existing WordPress sites from other hosting platforms. And it’s very good! The process boils down to:

The process can also be seen in this Avada video:

The plugin copies all the amazonwebshark server files, scripts and database objects in a process that takes about five minutes. SiteGround then provides a temporary URL for testing and performance checks:

2022 12 02 SiteGroundMigratonComplete

After completely migrating amazonwebshark to SiteGround, the next step involves telling the amazonwebshark domain where to find the new server. Time for some DNS!

DNS Migration

In this section, I update the amazonwebshark DNS records with the SiteGround name servers.

I repointed the existing amazonwebshark NS record from Bluehost to SiteGround by updating the values in Route 53 from this:

2022 12 02 Route53Bluehost

To this:

2022 12 02 Route53SiteGround

My change then needed to propagate through the Internet. Internet Service Providers update their records at different rates, so changes can take up to 72 hours to complete worldwide.

Free DNS checking tools like WhatIsMyDNS can perform global checks on a domain name’s IP address and DNS record information. The check below was done after around 30 hours, by which time most of the servers were returning SiteGround IPs:

2022 12 06 DNSPropagationCheck

Any Problems?

First, the good news. There was no downtime while migrating amazonwebshark to SiteGround! During the migration, DNS queries were resolved by either Bluehost’s or SiteGround’s name servers. Both platforms had amazonwebshark data, so both could answer DNS queries.

Additionally, as I set a change freeze on amazonwebshark until the migration was over, there was no lost or orphaned content.

I did lose some WPStatistics hit statistics data though. There is no data for December 03 and December 04:

2022 12 18 WPStatisticsHits

This was my fault. The DNS propagation took longer than it should have because of a misunderstanding on my part!

So why was data lost? WPStatistics stores data in tables in the site’s MySQL database. When I first migrated my data on December 02, the Bluehost and SiteGround tables were the same. After that point, Bluehost continued to serve amazonwebshark until December 05, and wrote its statistics in the Bluehost MySQL tables.

It was only after I corrected my DNS mistake that SiteGround could start serving content and writing statistics on the SiteGround MySQL tables. So SiteGround didn’t record anything for December 03 and December 04, and as no additional data migration was done the statistics that Bluehost recorded never made it to the SiteGround tables.

I can recover this if I want to though. I took a full backup of my Bluehost data before ending the contract. That included a full backup of the Bluehost MySQL database with the WPStatistics tables. I’ll take a look at the tables at some point, see how the data is arranged and decide from there.

Future Plans

I’m considering moving amazonwebshark to a serverless architecture in 2023. While the migration was a success, servers still have inherent problems:

  • Servers can break or go offline.
  • They can be hacked.
  • They can be over or under-provisioned.

Serverless infrastructure could remove those pain points. I don’t use any WordPress enterprise features, and amazonwebshark could exist very well as an event-driven static website. Tools like Hugo and Jekyll are designed for the job and documented well, and people like Kendra Little and Chrissy LeMaire have successfully transitioned their blogs to serverless infrastructures.

The biggest challenge here isn’t architectural. If I moved to a serverless architecture, I would want something similar to the Yoast SEO analysis plugin. This plugin has really helped me improve my posts, and by extension has made them more enjoyable to write.

I’ve seen lots of serverless tooling for migrating resources and serving content, but not so much for SEO guidance and proofreading. Any amazonwebshark serverless migration would be contingent on finding something decent along these lines. After all, if the blog becomes a pain to write for then what’s the point?

Summary

In this post, I examined the process of migrating amazonwebshark to SiteGround and gave an overview of the processes involved.

I’m very happy with how things went overall! The heavy lifting was done for me, both companies were open and professional throughout and what could have been a daunting process was made very simple!

If this post has been useful, please feel free to follow me on the following platforms for future updates:

Thanks for reading ~~^~~