<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Steven Cooney</title>
    <description>The latest articles on Forem by Steven Cooney (@theyorkshiredev).</description>
    <link>https://forem.com/theyorkshiredev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F533709%2F5659dc53-cb7e-4887-a444-7d9ccf1a31c4.jpeg</url>
      <title>Forem: Steven Cooney</title>
      <link>https://forem.com/theyorkshiredev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/theyorkshiredev"/>
    <language>en</language>
    <item>
      <title>Setting up Pi-hole on Ubuntu Mac Mini</title>
      <dc:creator>Steven Cooney</dc:creator>
      <pubDate>Fri, 05 Feb 2021 20:26:14 +0000</pubDate>
      <link>https://forem.com/theyorkshiredev/setting-up-pi-hole-on-ubuntu-mac-mini-176e</link>
      <guid>https://forem.com/theyorkshiredev/setting-up-pi-hole-on-ubuntu-mac-mini-176e</guid>
      <description>&lt;p&gt;I've wanted to set up a Pi-hole on my home network for a while but never found the time. Similarly, my old Mac mini has been laying around for a few years now, and I have meant to make use of it. After the recent announcement that MacOS Big Sur will not be compatible with my 2012 Mac mini, I have decided to turn my mini into an Ubuntu server and install PiHole.&lt;/p&gt;

&lt;h1&gt;
  
  
  Hardware
&lt;/h1&gt;

&lt;p&gt;As touched upon already, I re-purposed an old 2012 Mac Mini. The Mac has an i5-3210M processor, 8GB of RAM and a 500GB hard disk. Using a Mac mini that is a small form-factor allowed me to position it nicely underneath my router.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HWDE5Usp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/q0jyvtgv1u00jvvvizrr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HWDE5Usp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/q0jyvtgv1u00jvvvizrr.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Install &amp;amp; Configure Ubuntu Server
&lt;/h1&gt;

&lt;p&gt;The first step was to grab Ubuntu server and follow the installation instructions from the &lt;a href="https://ubuntu.com/tutorials/install-ubuntu-server#1-overview"&gt;official website&lt;/a&gt;. I used a USB stick as the installation media. To boot from the USB, hold down the &lt;code&gt;⌘ Command&lt;/code&gt; key when powering on the Mac to open up the boot menu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; During the install when specifying the disk to format the full disk was not selected by default. Therefore I had to expand the default size to utilise the whole disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update the System
&lt;/h2&gt;

&lt;p&gt;After the initial installation, I updated everything before continuing any further.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Postfix for Email
&lt;/h2&gt;

&lt;p&gt;To keep the server up to date unattended-upgrades are used. Reports from unattended-upgrades get emailed using Postfix.&lt;/p&gt;

&lt;p&gt;To install Postfix and initial config I ran the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;postfix mailutils
&lt;span class="nb"&gt;cp&lt;/span&gt; /usr/share/postfix/main.cf.debian /etc/postfix/main.cf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used SendGrid as a relay host and followed their documentation for &lt;a href="https://sendgrid.com/docs/for-developers/sending-email/postfix/"&gt;configuring Postfix&lt;/a&gt;. Below was the configuration I added to &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
smtp_tls_security_level = encrypt
header_size_limit = 4096000
relayhost = [smtp.sendgrid.net]:587
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To authenticate against Sendgrid using an API key, I created &lt;code&gt;/etc/postfix/sasl_passwd&lt;/code&gt; to store the API key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[smtp.sendgrid.net]:587 apikey:&amp;lt;yourSendGridApiKey&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the password file had the correct ownership and before restarting postfix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /etc/postfix/sasl_passwd
postmap /etc/postfix/sasl_passwd
systemctl restart postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test Postfix by sending an email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Just testing postfix"&lt;/span&gt; | mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"[Test Email]"&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Unattended Upgrades
&lt;/h2&gt;

&lt;p&gt;As mentioned before, to ensure the Ubuntu Server is always patched, I configured unattended-upgrades which installs updates without manual interaction.&lt;/p&gt;

&lt;p&gt;Install unattended-upgrades:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;unattended-upgrades apt-listchanges
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frequency Configuration
&lt;/h3&gt;

&lt;p&gt;The frequency of updates I used was based upon &lt;a href="https://gist.github.com/roybotnik/b0ec2eda2bc625e19eaf#frequency-configuration"&gt;this setup&lt;/a&gt; which was suitable for my needs.&lt;br&gt;
Within &lt;code&gt;/etc/apt/apt.conf.d/20auto-upgrades&lt;/code&gt; I set the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::Unattended-Upgrade "3";
APT::Periodic::AutocleanInterval "9";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;APT::Periodic::Update-Package-Lists "1";&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;I update the package lists daily. This is important because unattended-upgrades can fail if the sources are outdated.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APT::Periodic::Download-Upgradeable-Packages "1"&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;I also download the updates daily too rather than downloading them all in one go.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APT::Periodic::Unattended-Upgrade "3";&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Perform the installation every three days.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APT::Periodic::AutocleanInterval "9"&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Clean the package cache every nine days.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  unattended-upgrades Configuration
&lt;/h3&gt;

&lt;p&gt;In the unattended-upgrades configuration file, I set the &lt;code&gt;Unattended-Upgrade::Mail&lt;/code&gt; property to my email address.&lt;/p&gt;

&lt;p&gt;I also set automatic reboot and the time to reboot too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install &amp;amp; Setup PiHole
&lt;/h1&gt;

&lt;p&gt;The inspiration for setting up Pi-Hole came from reading a &lt;a href="https://scotthelme.co.uk/securing-dns-across-all-of-my-devices-with-pihole-dns-over-https-1-1-1-1/"&gt;blog post&lt;/a&gt; by &lt;a href="https://twitter.com/Scott_Helme"&gt;Scott Helme&lt;/a&gt;, where he outlined the steps he undertook. Alongside, setting up Pi-Hole to filter out advertisements on my home network, I also set up DNS over HTTPS (DoH). DoH facilitates the secure transmission of DNS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Cloudflared
&lt;/h2&gt;

&lt;p&gt;Cloudflared is used as a proxy to enable DNS over HTTPS. Pi-Hole has good &lt;a href="https://docs.pi-hole.net/guides/dns/cloudflared/"&gt;documentation for installing Cloudflared&lt;/a&gt;. I installed cloudflared the "automatic" way outlined in the link above, below are the steps I undertook.&lt;/p&gt;

&lt;p&gt;Install Cloudflared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.deb
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; ./cloudflared-stable-linux-amd64.deb
cloudflared &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To configure cloudflared, I used a config file to specify the upstream DNS location DoH requests should be routed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /etc/cloudflared/
vi /etc/cloudflared/config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;config.yml&lt;/code&gt; file content points to CloudFlare's DNS servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proxy-dns: true
proxy-dns-port: 5053
proxy-dns-upstream:
  - https://1.1.1.1/dns-query
  - https://1.0.0.1/dns-query
#Uncomment following if you want to also want to use IPv6 for external DOH lookups
  #- https://[2606:4700:4700::1111]/dns-query
  #- https://[2606:4700:4700::1001]/dns-query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; The DNS proxy port is needed later on.&lt;/p&gt;

&lt;p&gt;Once the config was in place, I installed cloudflared as a service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared service &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--legacy&lt;/span&gt;
systemctl start cloudflared
systemctl status cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test DNS routed via cloudflared works you can run &lt;code&gt;dig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig @127.0.0.1 &lt;span class="nt"&gt;-p&lt;/span&gt; 5053 google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing and configuring the Pi-Hole
&lt;/h2&gt;

&lt;p&gt;Installing Pi-Hole was trivial, I just ran the following command and proceeded through the install instructions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; It did not matter the default DNS configuration I choose since I override the setting after installation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://install.pi-hole.net | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the installation was complete, I updated a couple of configuration files to ensure Pi-Hole was routing DNS through the cloudflared service.&lt;/p&gt;

&lt;p&gt;Within &lt;code&gt;/etc/pihole/setupVars.conf&lt;/code&gt; remove the values for &lt;code&gt;PIHOLE_DNS_1&lt;/code&gt; and &lt;code&gt;PIHOLE_DNS_2&lt;/code&gt;. This stops Pi-hole from using the DNS configuration chosen when setting up Pi-hole.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
PIHOLE_DNS_1=
PIHOLE_DNS_2=
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also created an additional configuration file for &lt;code&gt;dnsmasq&lt;/code&gt; to route DNS through the cloudflared service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; The port (after &lt;code&gt;#&lt;/code&gt;) should match the DoH proxy port configured in cloudflared&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;/etc/dnsmasq.d/cloudflared-dns.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server=127.0.0.1#5053
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then restarted the pihole-FTL service to use the updated config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart pihole-FTL
systemctl status pihole-FTL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring devices to use Pi-Hole
&lt;/h2&gt;

&lt;p&gt;To enable all of my devices to utilise Pi-Hole, I chose to set the network DNS servers on my router so, all devices will be routed through Pi-Hole and DoH by default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IDF8U0Rh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vbpgpckbgy7dgv3hswvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IDF8U0Rh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vbpgpckbgy7dgv3hswvw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;A few resources that I used during my setup that may also be of use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/wnasich/71093e406c07ed3f03f63da8abd6ce97"&gt;Install outgoing email from Debian/Ubuntu system through SendGrid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sendgrid.com/docs/for-developers/sending-email/postfix/"&gt;Integrate Postfix with SendGrid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.debian.org/UnattendedUpgrades"&gt;Debian Unattended Upgrades Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/roybotnik/b0ec2eda2bc625e19eaf"&gt;@roybotnik Unattended Upgrades Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pi-hole.net/guides/dns/cloudflared/"&gt;Install Cloudflared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pi-hole.net/main/basic-install"&gt;Install Pi-Hole&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you installed and configured Pi-Hole? Do you have any feedback? Reach out and let's discuss.&lt;/p&gt;

</description>
      <category>project</category>
      <category>pihole</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Git Setup for OpenSource/Public Development</title>
      <dc:creator>Steven Cooney</dc:creator>
      <pubDate>Sat, 23 Jan 2021 15:13:41 +0000</pubDate>
      <link>https://forem.com/theyorkshiredev/git-setup-for-opensource-public-development-ml1</link>
      <guid>https://forem.com/theyorkshiredev/git-setup-for-opensource-public-development-ml1</guid>
      <description>&lt;p&gt;If you intend to develop in the public domain on opensource projects, you will need to set up Git. I'm going to outline my git configuration and discuss my approach.&lt;/p&gt;

&lt;h1&gt;
  
  
  Email Address
&lt;/h1&gt;

&lt;p&gt;When setting up Git, one of the first things you set up is your email address. Often not much thought is given to this step, however, if you use your private email address you are advertising your email address. Consequently, your email address could become subject to spam by a malicious actor who scrapes email address from git history.&lt;/p&gt;

&lt;p&gt;For example, below is a screenshot of Wes Bos' &lt;a href="https://github.com/wesbos/awesome-uses" rel="noopener noreferrer"&gt;awesome-uses&lt;/a&gt; repository. You can see several of the contributors have exposed their email addresses when committing to the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Famdaogbra2n4bh8zkxuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Famdaogbra2n4bh8zkxuf.png" alt="Image of Git Log"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Configuration
&lt;/h2&gt;

&lt;p&gt;I use the no-reply address for my GitHub account as my git email. On GitHub, you can find your no-reply email address in &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Emails&lt;/em&gt; &amp;gt; under "Keep my email addresses private".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0h8sp2cn5mk2d3q8d5p5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0h8sp2cn5mk2d3q8d5p5.png" alt="GitHub Email Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then set your git email using this address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git config --global user.email "30004860+TheYorkshireDev@users.noreply.github.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To avoid my email address from being exposed when performing actions through GitHub UI such as edits or merges, I check "Keep my email addresses private".&lt;/p&gt;

&lt;p&gt;Finally, I check "Block command line pushes that expose my email" which blocks pushes with commits that include personal email addresses.&lt;/p&gt;

&lt;h1&gt;
  
  
  Username
&lt;/h1&gt;

&lt;p&gt;When setting up Git, alongside email, you need to set your name. I would recommend using your first and surname for this option, which most people will probably do automatically. It is worth noting if you are thinking of using a pseudonym in Git, it can violate some open source projects contribution guidelines. One such example is the Docker project, so to err on the side of caution, it might be worth sticking to your real name.&lt;/p&gt;

&lt;h1&gt;
  
  
  Commit Signing
&lt;/h1&gt;

&lt;p&gt;The final thing I have configured for open source development is commit signing. I'm not going to go over the advantages and disadvantages of commit signing in this post, for more details check out &lt;a href="https://softwareengineering.stackexchange.com/a/212216" rel="noopener noreferrer"&gt;this StackExchange post&lt;/a&gt; and sub-links. Not everyone agrees on an approach to commit signing, but I tend to sign all my commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate GPG Key
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Use the &lt;code&gt;no-reply&lt;/code&gt; email address when generating a GPG key.&lt;/p&gt;

&lt;p&gt;I recommend following GitHub's official guide for &lt;a href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/generating-a-new-gpg-key" rel="noopener noreferrer"&gt;generating GPG keys&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a GPG key to your GitHub Account
&lt;/h2&gt;

&lt;p&gt;You will need to have noted down your public key to upload to GitHub. If you didn't, run the following to retrieve it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --armor --export &amp;lt;EMAIL_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, I recommend following GitHub's official guide for &lt;a href="https://docs.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account" rel="noopener noreferrer"&gt;adding a GPG key to your GitHub account&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Local Environment
&lt;/h2&gt;

&lt;p&gt;The following commands configure Git to always sign commits with the GPG key you have just generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; You want the GPG key id you have just generated, run &lt;code&gt;gpg --list-secret-keys --keyid-format LONG&lt;/code&gt; to find it again if you didn't note it down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git config --global user.signingKey &amp;lt;REPLACE\_THIS\_WITH\_YOUR\_KEY\_ID&amp;gt;

git config --global commit.gpgSign true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify commit signing is configured, run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "test" | gpg --clearsign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;What's your Git configuration? Do you have any feedback? Reach out and let's discuss.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Azure Service Principals 101</title>
      <dc:creator>Steven Cooney</dc:creator>
      <pubDate>Tue, 12 Jan 2021 22:31:53 +0000</pubDate>
      <link>https://forem.com/theyorkshiredev/azure-service-principals-101-26if</link>
      <guid>https://forem.com/theyorkshiredev/azure-service-principals-101-26if</guid>
      <description>&lt;p&gt;I was recently talking to a colleague who was starting with Azure for the first time, we were discussing authentication methods, especially when using external tools. When interacting with Azure programmatically, you soon stumble across service principals, however, for someone new to Azure what are Service Principals and some of the useful commands used to manage them?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; for the snippets below, I am assuming that you have a single Azure Tenant and it has the default name of "Default Directory".&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a Service Principal?
&lt;/h1&gt;

&lt;p&gt;Azure uses Active Directory (AD) to manage users and other access to Azure services and resources. When a user logs onto Azure, they have a User identity object within AD that has associated permissions and roles dictating their access within Azure.&lt;/p&gt;

&lt;p&gt;Often there are requirements to have programmatic access to Azure resources and services. One example may be automated tools within CI/CD to deploy an application or access Azure services. Rather than creating a "dummy/fake" user identity within AD, we create a Service Principal.&lt;/p&gt;

&lt;p&gt;Service Principals can have their permission scoped to only interact with a particular set of resources or services. Furthermore, roles outline the actions that the service principal can perform on the resources or services.&lt;/p&gt;

&lt;h1&gt;
  
  
  List Service Principals
&lt;/h1&gt;

&lt;p&gt;There are tons of built-in Service Principals (SPs) within Azure such as SPs for Office 365 whether you use it or not. To find SPs that you've created, it's best filtering out to only include those within your Azure Tenant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"publisherName eq 'Default Directory'"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Create a Service Principal
&lt;/h1&gt;

&lt;p&gt;When creating a service principal, you should specify a name for easier management in the future. You should also only give it the minimum roles and scopes required to undertake the desired action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; service principals have a default expiry of 1 year from creation. You can specify how many years before expiry by providing the &lt;code&gt;--years&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create-for-rbac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyAwesomeApp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/subscriptions/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SubID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;/resourceGroups/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ResourceGroup1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/subscriptions/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SubID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;/resourceGroups/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ResourceGroup2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For all parameters, you can refer to the documentation for &lt;a href="https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac"&gt;az ad sp create-for-rbac&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Find Expired Service Principals
&lt;/h1&gt;

&lt;p&gt;There is a quota limit on Service Principals within an Azure Tenant not to mention it's probably good hygiene to clean up after yourself. So we sometimes want to find expired Service Principals to either delete or renew them.&lt;/p&gt;

&lt;p&gt;Below is a Powershell command to list all Service Principals that have expired. We first get a list of all Service Principals within our Tenant then loop over them checking the expiry date, only outputting those that have expired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; unlike other commands in this post this is a Powershell command, not Azure CLI, so if you are running Az CLI outside of Powershell you may want to use Azure's Cloud Shell to undertake this action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$spns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"publisherName eq 'Default Directory'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$spn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$spns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$spn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-lt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$spn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Renew/Update a Service Principal
&lt;/h1&gt;

&lt;p&gt;When Service Principals expire, they need to be renewed. Likewise, if the Service Principal secret is exposed, it must be regenerated. The command below resets the service principal generating a new secret key and expiry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; service principals have a default expiry of 1 year from creation. You can specify how many years before expiry by providing the &lt;code&gt;--years&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyAwesomeApp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For all parameters, you can refer to the documentation for &lt;a href="https://docs.microsoft.com/en-us/cli/azure/ad/sp/credential?view=azure-cli-latest#az_ad_sp_credential_reset"&gt;az ad sp credential reset&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Delete a Service Principal
&lt;/h1&gt;

&lt;p&gt;Once you have no use for a Service Principal, you should delete it removing access to Azure. Although I haven't touched upon it much in this post, it is possible to have multiple credentials assigned to a Service Principal. Multiple credentials are useful when you have more than one Azure Tenant.&lt;/p&gt;

&lt;p&gt;Below is a command to delete a Service Principal, all credentials, roles and scopes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; &lt;code&gt;--id&lt;/code&gt; can take the Service Principal ID or name&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyAwesomeApp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For all parameters, you can refer to the documentation for &lt;a href="https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_delete"&gt;az ad sp delete&lt;/a&gt; or deleting a single credential on a Service Principal &lt;a href="https://docs.microsoft.com/en-us/cli/azure/ad/sp/credential?view=azure-cli-latest#az_ad_sp_credential_delete"&gt;az ad sp credential delete&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you enjoyed this article, share it with your friends and colleagues!&lt;/p&gt;

</description>
      <category>azure</category>
      <category>azurecli</category>
    </item>
    <item>
      <title>Website &amp; Blog Stack</title>
      <dc:creator>Steven Cooney</dc:creator>
      <pubDate>Sat, 05 Dec 2020 15:47:44 +0000</pubDate>
      <link>https://forem.com/theyorkshiredev/website-blog-stack-2lje</link>
      <guid>https://forem.com/theyorkshiredev/website-blog-stack-2lje</guid>
      <description>&lt;p&gt;Here we go, my first blog post after recently adding blog functionality to my website &lt;a href="https://theyorkshire.dev"&gt;TheYorkshire.Dev&lt;/a&gt;. I'm going to go over why I redesigned my website, what I'm using, and some enhancements I want to add.&lt;/p&gt;

&lt;h1&gt;
  
  
  Old Stack &amp;amp; Setup
&lt;/h1&gt;

&lt;p&gt;My previous website was built in 2013 and was orange and grey. A refresh was long overdue. I created it primarily to assist in getting a placement year during my degree. It was pretty basic, built from HTML &amp;amp; CSS then deployed using FTP manually which was laborious, both modifying code and deploying.&lt;/p&gt;

&lt;p&gt;I hosted the website on a $5 a month Digital Ocean droplet upon which I had installed the LAMP stack. Having a virtual private server seemed like a good idea at the time, but it soon became a burden. If I'm honest, I lost care and didn't update the VPS as regularly as I should have so decided it was time to lift and shift the website to a friendlier platform.&lt;/p&gt;

&lt;p&gt;I moved the website over to Azure and utilised App Service. Azure App Service is a Platform as a Service (PaaS) offering, allowing managed hosting of applications for a variety of technologies. In addition to switching how I hosted the website, I also set up CI/CD with Azure DevOps (still a stupid name) for deploying the website easily whenever it is updated.&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspiration
&lt;/h1&gt;

&lt;p&gt;In all honesty, I have been working on and off at building this site for over a year. The first cut has been published for many months now although I've only just finished adding blog capability hence this post.&lt;/p&gt;

&lt;p&gt;Due to work commitments, it took me much longer than I wanted. The last nine months have been chaos with the pandemic as we are involved in the national NHS covid effort, so at the end of the day, I just wanted to log off my PC, kick back and relax.&lt;/p&gt;

&lt;p&gt;Last year, I was fortunate to see a couple of talks from &lt;a href="https://twitter.com/philhawksworth"&gt;Phil Hawksworth&lt;/a&gt; who works at Netlify and is a big advocate for JAMStack. I liked what I saw and decided that I should refresh my website. I also had a few other motives;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  I had some TheYorkshireDev logos a friend designed for me a few years back I wanted to incorporate on my website&lt;/li&gt;
&lt;li&gt;  I needed to get rid of the orange and grey and use a constant, clean colour scheme that links with my logos&lt;/li&gt;
&lt;li&gt;  I had a new domain to use&lt;/li&gt;
&lt;li&gt;  I wanted to try my hand at blogging needed a new solution&lt;/li&gt;
&lt;li&gt;  I wanted it to be a PWA, so if someone enjoys my blogging they could install the site as an app, potentially receiving notifications for blog posts (Still a TODO)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Current Stack &amp;amp; Setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Development
&lt;/h2&gt;

&lt;p&gt;I was inspired for the refresh by seeing JAMStack websites that didn't depend on webservers and could be statically generated. Since this was my first foray into static site generators, I did some googling to get up to speed. I found a nice tutorial &lt;a href="https://github.com/justinformentin/gatsby-v2-tutorial-starter"&gt;GatsbyJS tutorial&lt;/a&gt; that covered elements I wanted to incorporate in my shiny new site.&lt;/p&gt;

&lt;p&gt;Gatsby allowed me to use ReactJS and build the site with components. Using components avoids duplicating code which was one of the issues with my previous website. I styled the website using styled-components and Emotion, allowing me to scope my CSS, which I find much easier to manage.&lt;/p&gt;

&lt;p&gt;The extensive plugin ecosystem came in use for adding functionality to the website. For example, allowing me to write blog posts in Markdown and transforming them to HTML, to be embedded during a build with a little help from GraphQL. I also used plugins to assist in optimising images, improving SEO and adding PWA functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD &amp;amp; Hosting
&lt;/h2&gt;

&lt;p&gt;The source code for the website is publically available on GitHub in a repository. I had planned to have the website and blog posts in separate repositories but have decided to keep them together for now (see planned enhancements).&lt;/p&gt;

&lt;p&gt;I have continued to use Azure DevOps for building and deploying the website. New commits on the website trigger a build within Azure DevOps and then subsequently deploys the code. Commits on the master branch deploy the code to production whereas commits on other branches deploy to a staging location.&lt;/p&gt;

&lt;p&gt;I am hosting my website on Netlify. I was going to continue using Azure, however, setting up custom domain without a subdomain isn't possible. Netlify also provides free HTTPS certificates backed by let's encrypt, which is something I would have had to set up and configure within Azure. For the staging environment, I am using Azure blob storage fronted by a CDN for verifying website changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planned Enhancements
&lt;/h2&gt;

&lt;p&gt;There are a few additions I'd like to make to the website in future.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Pull blog posts into a second repository&lt;/li&gt;
&lt;li&gt;  Blog post comments&lt;/li&gt;
&lt;li&gt;  Blog Pagination&lt;/li&gt;
&lt;li&gt;  Blog PWA Notifications&lt;/li&gt;
&lt;li&gt;  Ability to subscribe to the blog&lt;/li&gt;
&lt;li&gt;  Switch CI/CD to GitHub Workflows&lt;/li&gt;
&lt;li&gt;  Configure the website to use CSP and other HTTP headers&lt;/li&gt;
&lt;li&gt;  Update staging site to use either Netlify or Azure Static Web Apps&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;What's your tech stack? Do you have any feedback? Reach out and let's discuss.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, share it with your friends and colleagues!&lt;/p&gt;

</description>
      <category>blog</category>
      <category>website</category>
      <category>techstack</category>
    </item>
  </channel>
</rss>
