<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Finn Greig</title>
	<subtitle>A Software Engineer in New Zealand</subtitle>
	
	<link href="https://finngreig.com/feed/feed.xml" rel="self"/>
	<link href="https://finngreig.com/"/>
	<updated>2022-11-14T00:00:00Z</updated>
	<id>https://finngreig.com/</id>
	<author>
		<name>Finn Greig</name>
		<email>hello@finngreig.com</email>
	</author>
	
	<entry>
		<title>Running Honk on Alpine Linux</title>
		<link href="https://finngreig.com/posts/2022-11-14/"/>
		<updated>2022-11-14T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2022-11-14/</id>
		<content type="html">&lt;p&gt;With all the recent talk about the fediverse now that Twitter is on fire, I decided to give it a go by running my own instance. There&#39;s a lot of benefits to this - considering that you&#39;re joining a decentralised social networking system, you might as well own your data, and also start with a clean slate - you don&#39;t need to worry about other servers having already blocked the server that you&#39;re only just getting set up on.&lt;/p&gt;
&lt;p&gt;I decided to go with an AWS t3a.nano instance running Alpine Linux for my server, and I&#39;ll be running &lt;a href=&quot;https://humungus.tedunangst.com/r/honk&quot;&gt;Honk&lt;/a&gt; on it - this is a minimalist web application written in Go that interfaces with the rest of the fediverse using &lt;a href=&quot;https://activitypub.rocks/&quot;&gt;ActivityPub&lt;/a&gt; (the underlying standard that powers Mastodon, Pleroma, Akkoma, Misskey, etc.)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ap-southeast-2.console.aws.amazon.com/ec2/home#Images:visibility=public-images;imageId=ami-0b940c2f216275bb8&quot;&gt;This is the AMI image I used&lt;/a&gt; - up to date ones can always be found on the &lt;a href=&quot;https://alpinelinux.org/cloud&quot;&gt;Alpine website&lt;/a&gt;. Just ensure that your security group has TCP/80 and TCP/443 open to the world :)&lt;/p&gt;
&lt;p&gt;The Gist with the scripts and configs can be found &lt;a href=&quot;https://gist.github.com/finngreig/5991cd26defb1d41ca22e949c6d5d77e&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If all has gone well, when you load your domain you should get the purple Honk interface, and be able to log in. :)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2022-11-14/1.png&quot; alt=&quot;Honk screenshot&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Locking It Down? Easy Tools to Help You Develop Secure Software</title>
		<link href="https://finngreig.com/posts/2022-01-09/"/>
		<updated>2022-01-09T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2022-01-09/</id>
		<content type="html">&lt;p&gt;If you&#39;re like me, sometimes when you&#39;re working on bringing a great idea to life security can be a bit of an afterthought. You have already thought about the big stuff like TLS and implementing the right authentication and authorisation system, but there can still be blind spots - for example, how your application is handling user data flow, or how it&#39;s exposing that data to the rest of your application&#39;s code. Luckily there are tools that can help you with secure development, and I&#39;ll show you some of the big ones out there.&lt;/p&gt;
&lt;h2 id=&quot;1-sonarqube&quot; tabindex=&quot;-1&quot;&gt;1. SonarQube &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2022-01-09/#1-sonarqube&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sonarqube.org/&quot;&gt;SonarQube&lt;/a&gt; is a hosted automatic static analysis tool that detects bugs, vulnerabilities and code smells in your code. It works great with your existing workflow - for example, when you make a pull request, it can automatically check for code smells in your work and produces a quality report which can appear in the conversation over on GitHub (or whichever version control system you use).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2022-01-09/1.png&quot; alt=&quot;SonarQube Screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You&#39;re probably thinking &lt;em&gt;&amp;quot;What&#39;s a code smell? Code doesn&#39;t smell!&amp;quot;&lt;/em&gt; 👃 While it isn&#39;t a fragrance, a code smell is a hint that there could be deeper problems in your code. By fixing code smells when they occur, this can help to prevent critical paths for vulnerabilities forming in your code that can be used for exploitation at some point. These are generally small things, like how you could be handling a string, or uncontrolled side effects of a function.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;P.S. If anyone knows the term for some code that has a lot of smells, please let me know. Is the code stinky? Pungent? Who knows!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;2-sonarlint&quot; tabindex=&quot;-1&quot;&gt;2. SonarLint &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2022-01-09/#2-sonarlint&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you don&#39;t want to actually set up a SonarQube instance and you just need something simple that can work, &lt;a href=&quot;https://www.sonarlint.org/&quot;&gt;SonarLint&lt;/a&gt; is great. It&#39;s made by the same team behind SonarQube but it&#39;s a text editor/IDE plugin that can run standalone without SonarQube and it highlights smells while you&#39;re writing code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2022-01-09/2.png&quot; alt=&quot;SonarLint Screenshot&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;3-dependabot&quot; tabindex=&quot;-1&quot;&gt;3. Dependabot &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2022-01-09/#3-dependabot&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Enabled by default on GitHub repositories, Dependabot automatically scans dependencies in Ruby, JavaScript, Python, PHP, Elixir, Elm, Go, Rust, Java and .NET projects, and if any of them have been updated to address security vulnerabilities, it&#39;ll update your dependency definitions to use the new versions. You can even &lt;a href=&quot;https://github.com/dependabot/dependabot-core#setup&quot;&gt;run it yourself&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>We Need To Talk About Technical Debt</title>
		<link href="https://finngreig.com/posts/2022-01-05/"/>
		<updated>2022-01-05T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2022-01-05/</id>
		<content type="html">&lt;p&gt;Although I have had a short career in tech so far, the list of technical debt that Ive come across is not. Even though Ive only had a few odd solo jobs and internships before my current role, which is my first permanent one, there&#39;s been plenty of technical debt along the way.&lt;/p&gt;
&lt;p&gt;Generally software businesses follow fairly standard Agile practices - in my current role, we have a mixture of Scrum and Kanban teams - and each of these teams have the usual rituals like standup, planning, refinement and retro.&lt;/p&gt;
&lt;p&gt;In Agile, product owners are seen as the interface between executives/project sponsors, and the developers. They communicate to the developers the business requirements of a product they&#39;re building to make sure its made as the customer needs, and then the developers can communicate progress and technicalities that might come up along the way, and then they can communicate this to the project sponsors.&lt;/p&gt;
&lt;p&gt;When developers mention technical debt that they need to deal with, its possible that the product owner can minimise the importance of it repeatedly, often because feature x needs to be developed or deadline y for something is coming up and the customer wants a million other things done by then.&lt;/p&gt;
&lt;p&gt;Technical debt cannot be minimised. As it increases, the effects it has compound, and when future code relies on indebted code, the work required to finally deal with it really increases. If the debt is something that isn&#39;t feature specific and its effects are present on all new code being written, developers can become hesitant to work on something that could possibly fall over at any point in the future - it could be years away, or it could even happen tomorrow. It can also hinder DX (developer experience) - for example, a temporary stopgap that introduces few extra steps in the debugging and execution cycle could become permanent, and developer happiness can degrade significantly.&lt;/p&gt;
&lt;p&gt;Ideally we should have a development cycle that focuses on constantly working down technical debt each day in some way, and occasionally slowing development on new features and stories in favour of this. Product owners can help a lot with this, as they can convince executives/project sponsors that taking care of some of the technical debt is needed in order to start or complete work on a new feature/story. If the product owner is unwilling to do this, then the developers on the team should band together and negotiate that they deal with technical debt before starting on new work.&lt;/p&gt;
&lt;p&gt;Sometimes we have to pass up the opportunity to make another dollar in order to ensure long-term developer happiness and stability.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Review: GitHub Copilot Technical Preview</title>
		<link href="https://finngreig.com/posts/2021-11-24/"/>
		<updated>2021-11-24T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-11-24/</id>
		<content type="html">&lt;p&gt;About a month ago, I got invited to the GitHub Copilot Technical Preview. It is essentially code autocomplete on steroids for Visual Studio Code - behind the scenes it&#39;s built upon an OpenAI machine learning model named Codex, which was trained using public code from GitHub repositories and Stack Overflow answers. It works with a variety of languages - so far I&#39;ve mainly used it with JavaScript code.&lt;/p&gt;
&lt;p&gt;Usage is fairly simple - once you install the plugin from the VS Code Extensions Marketplace, you just need to login with your GitHub account - this is to ensure that you&#39;re eligible to access the technical preview. Since it collects usage data for improvement, a nice feature that it has is that it&#39;s automatically disabled when inside of anything that could be some sort of config or secrets file - for me, when I was in GitHub Actions yaml files and also .env files, it was disabled. Once you&#39;re in a file it&#39;s already running - as you write code documentation and function signatures it begins to suggest autocompletions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-11-24/1.png&quot; alt=&quot;Autocomplete Preview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you&#39;re happy with the suggestion, you can press Tab to accept it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-11-24/2.png&quot; alt=&quot;Autocomplete Filled&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(I tried using &lt;code&gt;Shift + [&lt;/code&gt; and &lt;code&gt;Shift + ]&lt;/code&gt; to select other suggestions but it wouldn&#39;t work for me at the time.)&lt;/p&gt;
&lt;p&gt;I&#39;m very happy with this - I see how it significantly speeds up development, especially considering that you could have one less window open as you wouldn&#39;t need to look at Stack Overflow or documentation when trying to figure out the best way to complete common tasks in code. However, I wouldn&#39;t say it&#39;s ready to be used as a daily driver at work yet, there is no way to run it offline and have it not send usage statistics and potentially your code to a third party server - I can imagine quite a few employers wouldn&#39;t be happy with this, especially when dealing with sensitive IP in a competitive or national security space. It&#39;s very handy for working on personal projects though.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Tech Culture Fatigue</title>
		<link href="https://finngreig.com/posts/2021-11-09/"/>
		<updated>2021-11-09T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-11-09/</id>
		<content type="html">&lt;p&gt;Having a bit of a knack with technology as a kid had me wanting to work in the field when I got older, and the future was looking so bright - we were meant to propel the world forward, solve big ticket problems and make things more accessible for everyone. However, being at that point now, I am left somewhat jaded (a bit of an understatement).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web 1.0: the web is for sharing information!&lt;/p&gt;
&lt;p&gt;Web 2.0: but what if we could make money too?&lt;/p&gt;
&lt;p&gt;Web 3.0: the web is only for making money.&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://twitter.com/Foone/status/1455601328479092739&quot;&gt;foone (@Foone) November 2, 2021&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Originally when what was dubbed &amp;quot;Web 1.0&amp;quot; came around, the world wide web was a bleeding edge concept. Only those lucky enough to have access to a computer with the right networking setup could use it. Pages were incredibly simple, consisting of mostly text and &lt;em&gt;hyperlinks&lt;/em&gt; - ways to click around to get to other pages, whether they be your own or someone else&#39;s. There was no Google, and everyone was happy about just being able to get information out there to other people more or less instantaneously - there was no need to wait for a postie to deliver letters anymore, or to deal with fax machines. Your email address was the new address. Over time, as prices for commodity computing hardware decreased, more and more people were able to enjoy this.&lt;/p&gt;
&lt;p&gt;Those were some pretty good times for me - naturally due to my age, I only got in on it at the end of the era. I can still fondly remember how the computer would need to scream before you could use the web (foreshadowing), or how instant messaging took off and became an instant hit. A decade and a half later, I can still hear the sound MSN Messenger would play when my brothers received new messages.&lt;/p&gt;
&lt;p&gt;Then started &amp;quot;Web 2.0&amp;quot;. JavaScript and Adobe Flash started gaining momentum, and websites now had interactive content. Then, the ads started to come. Much like classifieds in a newspaper, at first we thought &lt;em&gt;&amp;quot;sure, why not - after all, they need an income to keep their site running and earn a living.&amp;quot;&lt;/em&gt; But then the big corporations started to notice, and they got onto publishing content and building successful internet-facing businesses as well. Over time there were more and more flashy ads and less and less of the content you were actually consuming - &lt;em&gt;&amp;quot;hey, wait a minute! what&#39;s going on here?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This led us to where we are roughly now today, but we have a lot more issues on top of that. Data collection for targeting ads to specific people, addictive feedback loops from dark design patterns intended to keep people using something and chemically craving it so that ad revenue can be maximised, and collateral damage from these systems, for example, letting misinformation and disinformation run amok so that, once again, that juicy revenue can keep on flowing in.&lt;/p&gt;
&lt;p&gt;Now we&#39;re onto &amp;quot;Web 3.0&amp;quot; - the idea that everything online has to be decentralised and kept on something called a &amp;quot;blockchain&amp;quot; (which is just a term to obfuscate the fact that it&#39;s just a very convoluted linked list), and it&#39;s all about making money with tech in any way. Images are now &amp;quot;non fungible tokens&amp;quot;, exactly the same but now you get a receipt for owning something that can be copied infinite times at no cost. All this sort of stuff does is just chew electricity and wear out computer parts when it could be done another way, or in the case of non fungible tokens, just not be done in the first place. It&#39;s all just a big grift. In this stage of the game, even &lt;a href=&quot;https://doctorow.medium.com/dishwashers-have-become-iphones-7f8f53111736&quot;&gt;dishwashers have become iPhones&lt;/a&gt;. DRM (a.k.a. proprietary hardware lockouts) is in vogue now, reducing re-usability and repair-ability for things that would normally last consumers a very long time.&lt;/p&gt;
&lt;p&gt;Stuff like this makes me think about the &lt;a href=&quot;https://www.socialcapital.com/annual-letters/2019&quot;&gt;2019 Annual Letter for Social Capital&lt;/a&gt;, specifically the section on talent hoarding. Why are grads out here wasting their time and talent working on ad targeting for Big Tech, when their skills could be used to solve climate change, improve healthcare or make agriculture more efficient?&lt;/p&gt;
&lt;p&gt;We need to do better than this - while right now I am lucky enough to be in a role where I work on a variety of public transport solutions, these sorts of things sometimes make me feel embarrassed to be a technologist, especially when people ask me about them. We should be excited to share our developments with others, not anxious.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Gaining ADB Access on a Mobiwire Sakari</title>
		<link href="https://finngreig.com/posts/2021-10-14/"/>
		<updated>2021-10-14T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-10-14/</id>
		<content type="html">&lt;p&gt;Last year a technology retailer here in New Zealand, PB Tech, was running a Black Friday sale, and I came across their deal for a Vodafone Smart A9 - a rebranded Mobiwire Sakari &amp;quot;feature phone&amp;quot; - for NZ$13. I had previously encountered the phone a few more weeks before this at a display stand in a Harvey Norman store, and from briefly messing around with it, I realised it was running a really pruned back older version of Android. I figured at the time that I might as well buy one and see what I could get it to do.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-10-14/1.png&quot; alt=&quot;PB Tech Screenshot&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;poking-around&quot; tabindex=&quot;-1&quot;&gt;Poking Around &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2021-10-14/#poking-around&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Sakari is a simple phone - it has a 240x320 TFT LCD screen, a VGA camera with flash, a 3x4 physical keyboard and 5-way navigator keypad, a 3.5mm headset jack, a microUSB connector, a micro SIM slot and a microSD slot. The manual references an optional second SIM - there must be a variant out there with dual SIM support, however, mine only has a single slot. The phone also comes with a removable 1000 mAh battery, a mono wired headset, and a charger (unfortunately the cable is integrated into the wall power supply, so you will need your own microUSB cable to connect it to a PC).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-10-14/2.png&quot; alt=&quot;Back of the Sakari&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My model is a &amp;quot;VFD 120&amp;quot; in the grey slate colour - note the single SIM slot labelled as &amp;quot;SIM 1&amp;quot;.&lt;/p&gt;
&lt;p&gt;Screenshot of the home screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-10-14/3.png&quot; alt=&quot;Home Screen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CPU-Z screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-10-14/4.png&quot; alt=&quot;CPU-Z Screenshot&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;gaining-adb-access&quot; tabindex=&quot;-1&quot;&gt;Gaining ADB Access &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2021-10-14/#gaining-adb-access&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;As SP Flash Tool is unsigned software whose origin I am not sure of (supposedly MediaTek but the site appears unofficial), I used a Windows VM to perform the steps below. An easy way to set up a temporary one is to use the &lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/&quot;&gt;VM images&lt;/a&gt; Microsoft provides for Edge browser testing. If you want you can download one from Microsoft and run it in your preferred virtualisation software. If you have trouble trying to get the VM to capture the USB device at the right time when plugging the phone in, you might need to just run it directly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://spflashtool.com/&quot;&gt;Download SP Flash Tool&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download the scatter file: (Updated link coming soon)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download the modified boot image: (Updated link coming soon)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open SP Flash Tool, select the &amp;quot;download&amp;quot; tab and select the scatter file as the &amp;quot;scatter-loading file&amp;quot;. What this does is provide SP Flash Tool with the details about the partition table on the phone&#39;s flash memory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the boot image file as the location of BOOTIMG by double clicking in the location cell of the BOOTIMG row. Ensure the dropdown is set to Download Only. Click the download button at the top.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://finngreig.com/posts/2021-10-14/5.png&quot; alt=&quot;SP Flash Tool&quot; /&gt;&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;
&lt;p&gt;With the phone powered off, plug it into the computer. Flashing should now begin. Once it is done, the phone should reboot - if not, perform a reboot manually by doing a reset.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the phone didn&#39;t flash, you likely need to install drivers for the MediaTek preloader. There are web links for these within the Drivers folder of the SP Flash Tool folder - the VCOM driver is likely most relevant for this. After it&#39;s installed try again from step 5.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access a shell on the phone through ADB by running &lt;code&gt;adb shell&lt;/code&gt; in a command line terminal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the command &lt;code&gt;settings put global install_non_market_apps 1&lt;/code&gt;. The phone will now allow you to install apps from APKs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Many thanks to Kugelblitz from the &lt;a href=&quot;https://discord.gg/94zngYa4a2&quot;&gt;Mobiwire Modding Discord server&lt;/a&gt;, who provided the scatter file and the modified boot image to get ADB working on the Sakari.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Simple Wireguard VPN Setup</title>
		<link href="https://finngreig.com/posts/2021-08-27/"/>
		<updated>2021-08-27T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-08-27/</id>
		<content type="html">&lt;p&gt;First, install Wireguard. For Ubuntu:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; wireguard&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to forward all traffic from clients, enable IP forwarding in the kernel.&lt;/p&gt;
&lt;p&gt;Next, create a config file at &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt; containing the following (you can also choose to allocate v6 addresses to clients):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Interface]
PrivateKey = &amp;lt;server_private_key_goes_here&amp;gt;
Address = 172.16.1.1/32
ListenPort = 51820
# Use the following for forwarding all traffic from clients:
# Change eth0 to your network interface if it differs
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; iptables -A INPUT -s 172.16.1.0/24 -p tcp -m tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT; iptables -A INPUT -s 172.16.1.0/24 -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = &amp;lt;client_public_key_goes_here&amp;gt;
AllowedIPs = 172.16.1.2/32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, on the client, create a config file containing this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Interface]
PrivateKey = &amp;lt;client_private_key_goes_here&amp;gt;
Address = 172.16.1.2/32
# A DNS server can be specified for the client to use when the tunnel is active, optional
DNS = 172.16.1.1

[Peer]
PublicKey = &amp;lt;server_public_key_goes_here&amp;gt;
# AllowedIPs = 0.0.0.0/0, ::/0 for forwarding all traffic
AllowedIPs = 172.16.1.1/32
Endpoint = &amp;lt;server_ip&amp;gt;:51820
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each client, add a new &lt;code&gt;[Peer]&lt;/code&gt; block to the server config with their public key and their IP as the allowed IP.&lt;/p&gt;
&lt;p&gt;To start the interface on the server and make it persistent on reboots, run:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; wg-quick@wg0
systemctl start wg-quick@wg0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To generate a new keypair use the following command:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;wg genkey &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tee&lt;/span&gt; x_private_key &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; wg pubkey &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; x_public_key&lt;/code&gt;&lt;/pre&gt;
</content>
	</entry>
	
	<entry>
		<title>Turning a Git Repository into a Simple File Host with Cloudflare Workers</title>
		<link href="https://finngreig.com/posts/2021-08-27-1/"/>
		<updated>2021-08-27T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-08-27-1/</id>
		<content type="html">&lt;p&gt;(post originally hosted elsewhere)&lt;/p&gt;
&lt;p&gt;Recently I used CloudFlare Workers for the first time to create the website you&#39;re reading right now (look at &lt;a href=&quot;https://fruitionsite.com/&quot;&gt;Fruition&lt;/a&gt; to learn how I did it), and I decided to try out writing some of my own workers to get an idea of what it could do.&lt;/p&gt;
&lt;p&gt;I was in need of a simple static file host to serve various small files - stuff like HTML, presentations, PDFs etc - and thought about how Git repository hosts generally allow you to view a file within a repository in its raw format, served directly to you without modifications. I figured that I could use a CloudFlare Worker to proxy a request to a raw file path on my Git hosting service, and rewrite any necessary headers to ensure that files are served to browsers correctly.&lt;/p&gt;
&lt;h2 id=&quot;heres-how-i-did-it&quot; tabindex=&quot;-1&quot;&gt;Here&#39;s how I did it: &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2021-08-27-1/#heres-how-i-did-it&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;If you do not already have an account, sign up at CloudFlare and follow the step-by-step instructions provided to set up your domain to use their services.&lt;/li&gt;
&lt;li&gt;Click the workers button at the top of the page&lt;/li&gt;
&lt;li&gt;Set up your worker subdomain - this can be anything you like (if prompted), then click the manage workers button&lt;/li&gt;
&lt;li&gt;Click the create worker button&lt;/li&gt;
&lt;li&gt;In the script editor on the left half of the page, replace the example script with this:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// add a link to the raw file content host pointed at your primary ref&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;REPO_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://raw.githubusercontent.com/exampleuser/examplerepo/main/&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PATH_REGEX&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/*YOUR DOMAIN HERE*/&lt;/span&gt;&#92;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;).+&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mimeTable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;html&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;css&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;jpeg&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/jpeg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;jpg&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/jpeg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;js&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/javascript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;json&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;png&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;txt&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
* Respond to the request
* @param {Request} request
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; req &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PATH_REGEX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; extension &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REPO_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contentDisposition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content-disposition&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentDisposition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content-disposition&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content-type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mimeTable&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;extension&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Click the save and deploy button, and confirm that you want to save and deploy&lt;/li&gt;
&lt;li&gt;Go back to your domain&#39;s dashboard, and click the workers button again like in step 2&lt;/li&gt;
&lt;li&gt;Click the add route button&lt;/li&gt;
&lt;li&gt;Specify the route that the worker function will serve git repository assets for (e.g. &lt;code&gt;files.exampledomain.com/*&lt;/code&gt;), select your worker function that you just created in the worker dropdown and then click save&lt;/li&gt;
&lt;li&gt;Click the DNS button at the top of the page&lt;/li&gt;
&lt;li&gt;Add a CNAME record, with the name set to the domain used in the worker route, and the target can be set to a non-existent record (e.g. &lt;code&gt;sinkhole.exampledomain.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Try and open a file in your git repository by visiting &lt;code&gt;https://&amp;lt;WORKER_ROUTE_DOMAIN&amp;gt;/&amp;lt;FILENAME&amp;gt;.&amp;lt;EXT&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your Git repository is now like a simple file hosting server - files can be loaded directly over HTTP now. Since the worker script removes the content-disposition header if it is present and corrects the content-type header, it can also be used as an asset source within HTML and CSS.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>The importance of &#39;small sanitisation&#39;</title>
		<link href="https://finngreig.com/posts/2021-03-14/"/>
		<updated>2021-03-14T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-03-14/</id>
		<content type="html">&lt;p&gt;I&#39;ve been working on a Python script that takes the contents of &lt;a href=&quot;https://teara.govt.nz/en&quot;&gt;Te Ara&lt;/a&gt; and puts them into a compressed archive, after being inspired by how the entirety of the English Wikipedia&#39;s text and the Simple English Wikipedia&#39;s text can be downloaded as 18GB and 201MB archives respectively (as at March 2021), and wanting to have my very own copy of Aotearoa&#39;s history.&lt;/p&gt;
&lt;p&gt;There were a variety of bugs along the way - I was missing calls to &lt;code&gt;.replace()&lt;/code&gt; to handle some Unicode character conversions in my file path sanitisation function, and my browser and IDE were confusing me by showing the same representations for an em dash and a minus, which wasn&#39;t helping.&lt;/p&gt;
&lt;p&gt;I had implemented multiprocessing to take care of saving each article as a PDF once the sitemap had been scraped into a list and all was running well at the time - it was 4x faster and blazing its way through them.&lt;/p&gt;
&lt;p&gt;However, a seemingly small bug came up in the tail end of execution. It was giving a &lt;code&gt;FileNotFoundError&lt;/code&gt; for &lt;code&gt;./section_places/southland/stewart_island/rakiura.pdf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This confused me to no end for 6 hours - at first, I thought it was an issue with the list of scraped articles and there was an invalid item in it, and then I thought it was a race condition because of how the multiprocessing interacted with filesystem changes. I started putting guard conditions in to wait for directory creation before moving on to saving the PDF, and when that didn&#39;t work I tried using the Lock class that the multiprocessing library provided, but to no avail - and on top of that my print statements weren&#39;t showing and the debugger really wasn&#39;t helping much.&lt;/p&gt;
&lt;p&gt;I tried filtering the scraped article list down to just the item for Stewart Island and my print statements also started working then (something to do with the processes is my guess) - and it turned out that the article title had a forward slash in it (&lt;code&gt;Stewart Island/Rakiura&lt;/code&gt;). Since I was using pathlib so that my paths could be POSIX compliant regardless of OS, it was interpreting &lt;code&gt;Stewart Island&lt;/code&gt; as the directory containing the file, while not actually being created before saving the PDF. I made one small change to my sanitisation function to replace the slash with an underscore and it was working again.&lt;/p&gt;
&lt;p&gt;I feel like this highlighted the importance of &#39;small sanitisation&#39; for me - that is, sanitisation that&#39;s not considered critical by usual good practice conventions - like correctly parameterising SQL statements that take user input on a web server, or escaping HTML to prevent XSS is. It&#39;s the small cases that can often be quite difficult - you could have a non-standard specification to conform to with no public libraries to help you, and while data in this case is most likely not user-submitted, you might need to update your specifications each time your dataset changes. Hell, you might even have the odd wild goose chase through your code every now and then. Garbage in, garbage out and all that.&lt;/p&gt;
&lt;p&gt;Stay safe, and keep on keeping those strings nice and clean.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Flamingo Scooters API</title>
		<link href="https://finngreig.com/posts/2021-02-13/"/>
		<updated>2021-02-13T00:00:00Z</updated>
		<id>https://finngreig.com/posts/2021-02-13/</id>
		<content type="html">&lt;p&gt;A while ago I was working on an open-source scooter map web application, and I wanted to add the locations of electric scooters that are offered for hire here in New Zealand by a company named Flamingo. I reached out to their support, and they helpfully provided me with details about their public API.&lt;/p&gt;
&lt;p&gt;I figured someone else could also be looking for them, so here&#39;s how you can access it:&lt;/p&gt;
&lt;h2 id=&quot;api-details&quot; tabindex=&quot;-1&quot;&gt;API Details &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2021-02-13/#api-details&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Flamingo API returns data that follows the format of the GBFS standard (General Bikeshare Feed Specification). Each city has its own endpoint that contains information about the scooters available in the local area:&lt;/p&gt;
&lt;h3 id=&quot;wellington&quot; tabindex=&quot;-1&quot;&gt;Wellington &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2021-02-13/#wellington&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;https://api.flamingoscooters.com/gbfs/wellington/gbfs.json&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;christchurch&quot; tabindex=&quot;-1&quot;&gt;Christchurch &lt;a class=&quot;direct-link&quot; href=&quot;https://finngreig.com/posts/2021-02-13/#christchurch&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;https://api.flamingoscooters.com/gbfs/christchurch/gbfs.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;These endpoints provide information about the feeds that are available to consume. For example, if you wanted to see which scooters are available in Wellington, you would use the &lt;code&gt;free_bike_status&lt;/code&gt; endpoint:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://api.flamingoscooters.com/gbfs/wellington/free_bike_status.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Which should return something like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;bikes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;bike_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4129&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-41.276589&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;174.77937&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;current_range_meters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;27132&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1611545465&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;bike_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8025&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-41.288907&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;174.803936&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;current_range_meters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28917&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1611545501&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;bike_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8430&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-41.21483&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;174.804965&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;current_range_meters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25620&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1611545449&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;bike_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8240&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-41.312017&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;174.782053&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;current_range_meters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19635&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1611545412&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;bike_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8412&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-41.291181&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;174.79007&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;current_range_meters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24276&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1611545523&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;bike_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8252&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-41.278834&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;174.771693&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;current_range_meters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24990&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1611545464&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
	</entry>
</feed>
