aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJJ <nicetry@noemail.com>2025-03-19 14:56:26 +0000
committerJJ <nicetry@noemail.com>2025-03-19 14:56:26 +0000
commit16f52b7bef745097f7076dde76715db378b54343 (patch)
treecfcacda8adced2059dcc120d2bc2446d3c4f960a
first commit
-rw-r--r--.gitignore13
-rw-r--r--archetypes/default.md5
-rw-r--r--archetypes/projects.md5
-rw-r--r--content/_index.md9
-rw-r--r--content/about.md4
-rw-r--r--content/blog/_index.md3
-rw-r--r--content/blog/otp-command-line.md107
-rw-r--r--content/notes/aws-solutions-architect/01-intro.md27
-rw-r--r--content/notes/aws-solutions-architect/02.infra.md27
-rw-r--r--content/notes/aws-solutions-architect/03.iam.md113
-rw-r--r--content/notes/aws-solutions-architect/04.cli-sdk.md34
-rw-r--r--content/notes/aws-solutions-architect/10.ami.md21
-rw-r--r--content/notes/aws-solutions-architect/11.ec2-instance-store.md13
-rw-r--r--content/notes/aws-solutions-architect/12.scalability-availability.md36
-rw-r--r--content/notes/aws-solutions-architect/13.load-balancing-ELB.md191
-rw-r--r--content/notes/aws-solutions-architect/14.ssl-tls.md38
-rw-r--r--content/notes/aws-solutions-architect/15.RDS.md186
-rw-r--r--content/notes/aws-solutions-architect/5.ec2.md127
-rw-r--r--content/notes/aws-solutions-architect/6.ips.md26
-rw-r--r--content/notes/aws-solutions-architect/7.ec2-placement-groups.md16
-rw-r--r--content/notes/aws-solutions-architect/8.eni.md27
-rw-r--r--content/notes/aws-solutions-architect/9.ec2-storage.md102
-rw-r--r--content/notes/index.njk40
-rw-r--r--content/projects/indexcooking.md10
-rw-r--r--content/projects/polypubs.md8
-rw-r--r--content/snippets/11ty-fetch-put-request.md50
-rw-r--r--content/snippets/algolia-copy-between-apps.md27
-rw-r--r--content/snippets/batch-compress-images-magick.md13
-rw-r--r--content/snippets/batch-rename-files.md11
-rw-r--r--content/snippets/browse-update-algolia-index.md53
-rw-r--r--content/snippets/jq-convert-jsonl-json.md15
-rw-r--r--content/snippets/jq-count-json-items.md9
-rw-r--r--content/snippets/loop-files-rename.md13
-rw-r--r--content/snippets/rip-edit-audio.md33
-rw-r--r--content/snippets/tailwind-descendent-has.md19
-rw-r--r--hugo.toml12
-rw-r--r--layouts/404.html6
-rw-r--r--layouts/_default/baseof.html38
-rw-r--r--layouts/_default/list.html44
-rw-r--r--layouts/_default/single.html17
-rw-r--r--layouts/index.html3
-rw-r--r--layouts/notes/list.html12
-rw-r--r--layouts/partials/custom_body.html3
-rw-r--r--layouts/partials/custom_head.html3
-rw-r--r--layouts/partials/favicon.html2
-rw-r--r--layouts/partials/footer.html1
-rw-r--r--layouts/partials/header.html7
-rw-r--r--layouts/partials/nav.html9
-rw-r--r--layouts/partials/seo_tags.html22
-rw-r--r--layouts/projects/list.html40
-rw-r--r--layouts/robots.txt2
-rw-r--r--layouts/snippets/list.html40
-rw-r--r--static/images/avatar/avatar.jpgbin0 -> 233004 bytes
-rw-r--r--static/images/aws/2025-02-10-19-51-19.pngbin0 -> 2620044 bytes
-rw-r--r--static/images/aws/alb-query.pngbin0 -> 99130 bytes
-rw-r--r--static/images/aws/alb.pngbin0 -> 108138 bytes
-rw-r--r--static/images/aws/aurora-cluster.pngbin0 -> 256079 bytes
-rw-r--r--static/images/aws/aurora-custom-endpoint.pngbin0 -> 225677 bytes
-rw-r--r--static/images/aws/aurora-ml.pngbin0 -> 245439 bytes
-rw-r--r--static/images/aws/aurora-read-scaling.pngbin0 -> 205919 bytes
-rw-r--r--static/images/aws/aurora-scaling.pngbin0 -> 131577 bytes
-rw-r--r--static/images/aws/czlb.pngbin0 -> 408653 bytes
-rw-r--r--static/images/aws/glb-2.pngbin0 -> 166551 bytes
-rw-r--r--static/images/aws/glb.pngbin0 -> 24120 bytes
-rw-r--r--static/images/aws/loadbalancer.pngbin0 -> 77358 bytes
-rw-r--r--static/images/aws/moss.pngbin0 -> 1500816 bytes
-rw-r--r--static/images/aws/multi-az-rds.pngbin0 -> 132142 bytes
-rw-r--r--static/images/aws/nlb.pngbin0 -> 435159 bytes
-rw-r--r--static/images/aws/rds-replicas.pngbin0 -> 164477 bytes
-rw-r--r--static/images/aws/sni.pngbin0 -> 137922 bytes
-rw-r--r--static/images/aws/ssl.pngbin0 -> 127803 bytes
-rw-r--r--static/images/favicon/android-chrome-192x192.pngbin0 -> 48623 bytes
-rw-r--r--static/images/favicon/android-chrome-512x512.pngbin0 -> 247617 bytes
-rw-r--r--static/images/favicon/apple-touch-icon.pngbin0 -> 43212 bytes
-rw-r--r--static/images/favicon/favicon-16x16.pngbin0 -> 761 bytes
-rw-r--r--static/images/favicon/favicon-32x32.pngbin0 -> 2252 bytes
-rw-r--r--static/images/favicon/favicon.icobin0 -> 15406 bytes
-rw-r--r--static/images/favicon/site.webmanifest1
-rw-r--r--static/style.css153
79 files changed, 1846 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7b62a71
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# Generated files by hugo
+/public/
+/resources/_gen/
+/assets/jsconfig.json
+hugo_stats.json
+
+# Executable may be added to repository
+hugo.exe
+hugo.darwin
+hugo.linux
+
+# Temporary lock file while building
+/.hugo_build.lock \ No newline at end of file
diff --git a/archetypes/default.md b/archetypes/default.md
new file mode 100644
index 0000000..25b6752
--- /dev/null
+++ b/archetypes/default.md
@@ -0,0 +1,5 @@
++++
+date = '{{ .Date }}'
+draft = true
+title = '{{ replace .File.ContentBaseName "-" " " | title }}'
++++
diff --git a/archetypes/projects.md b/archetypes/projects.md
new file mode 100644
index 0000000..25b6752
--- /dev/null
+++ b/archetypes/projects.md
@@ -0,0 +1,5 @@
++++
+date = '{{ .Date }}'
+draft = true
+title = '{{ replace .File.ContentBaseName "-" " " | title }}'
++++
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..9ae96f3
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,9 @@
++++
+title = "Homepage"
++++
+
+This is my personal <a href="/blog">blog</a>, where I can jot down my thoughts on life, software and things i'm working on.
+
+I also use it as a personal references for <a href="/snippets">code snippets</a> I routinely re-use and <a href="/notes">notes</a> on the things I am currently studying.
+
+When i'm not doing this, i'm probably playing chess.
diff --git a/content/about.md b/content/about.md
new file mode 100644
index 0000000..dc399c3
--- /dev/null
+++ b/content/about.md
@@ -0,0 +1,4 @@
++++
+title = "about"
+menu = "main"
++++
diff --git a/content/blog/_index.md b/content/blog/_index.md
new file mode 100644
index 0000000..34651ab
--- /dev/null
+++ b/content/blog/_index.md
@@ -0,0 +1,3 @@
++++
+title = "Blog"
++++
diff --git a/content/blog/otp-command-line.md b/content/blog/otp-command-line.md
new file mode 100644
index 0000000..86127eb
--- /dev/null
+++ b/content/blog/otp-command-line.md
@@ -0,0 +1,107 @@
++++
+title = "OTP from the command line (AKA ditch your smartphone)"
+description = "Use the command line for your OTP needs, chuck your smartphone"
+tags = [
+ "linux"
+]
+date = 2024-07-08
++++
+
+I'm in the middle of an experiment to ditch my smartphone for a month.
+
+Anyway, i've found new resolve and i'm just using my Nokia dumbphone now, but a problem occurred today at work - how do I manage all my OTP needs? Usually I get a text or open up the Google Authenticator app to get access to systems.
+
+Enter `pass` and `pass otp`.
+
+## Command line password managment & OTP
+
+Turns out you can access all your OTP codes from the command line pretty easily if you're using a unix system.
+
+Fire up your terminal, first you need to install `pass`, which is the [standard local password manager](https://www.passwordstore.org/) for unix systems. We need pass in order to use pass otp, which is a pass plugin.
+
+For MacOS you can install using brew:
+
+```console
+$ brew install pass
+```
+
+Now you need to initialise pass, which uses one of your gpg keys to encrypt the password file on your local machine:
+
+If you don't know how to setup a gpg key, follow [this guide](https://dev.to/zemse/setup-gpg-on-macos-2iib), but it's basically:
+
+```console
+$ brew install gpg
+```
+
+```console
+$ gpg --gen-key
+```
+
+Then init pass:
+
+```console
+$ pass init "GPG key ID"
+```
+
+Now we need to install `pass otp`, [which is an extension](https://formulae.brew.sh/formula/pass-otp) of `pass` and will allow us to get one time passwords for all our logins. You can't have one without the other.
+
+```console
+$ brew install pass-otp
+```
+
+We also need to install `zbar` (more on that in a moment):
+
+```console
+$ brew install zbar
+```
+
+Once that's done, we're good to go.
+
+## Setting up your first OTP
+
+When you setup OTP for a website, you get given the QR code for your authenticator app to scan. To setup OTP from the command line we take the following steps:
+
+- Download the QR image
+- Use `zbarimg` to decode the URI of the QR code
+- Pass the URI to `pass otp`
+- OTP generation can now be done locally
+
+First, grab the QR code provided by whatever system you're logging in to, download it and `cd` into the directory with that image.
+
+Decode the QR code URI:
+
+```console
+$ zbarimg download.png
+```
+
+This will output a URI that looks something like this (using Soundclound QR code as an example)
+
+```
+QR-Code:otpauth://totp/SoundCloud?secret={RANDOMALPHANUMERICSTRING}
+```
+
+We need to rip everything after `QR-Code:`, so: `otpauth://totp/SoundCloud?secret={RANDOMALPHANUMERICSTRING}`.
+
+We now need to run our OTP command:
+
+```console
+$ pass otp add soundcloud
+```
+
+`soundcloud` here is the name of our OTP entry for this service.
+
+We will be prompted to add our `otpath:// URI`, add the URI from before.
+
+Done!
+
+Now anytime we are prompted by Soundcloud to add our OTP we simply run:
+
+```console
+$ pass otp soundcloud
+```
+
+We'll get back a six digit OTP.
+
+You could also automate this process with a shell script, i've created one over [here](https://github.com/JeremyJamesL/shell-scripts/blob/main/2fa-creater.sh).
+
+No smartphone, no problem. This actually has a benefit of not pulling you out of your workflow and being distracted while working / doing whatever.
diff --git a/content/notes/aws-solutions-architect/01-intro.md b/content/notes/aws-solutions-architect/01-intro.md
new file mode 100644
index 0000000..f9f1a7e
--- /dev/null
+++ b/content/notes/aws-solutions-architect/01-intro.md
@@ -0,0 +1,27 @@
+---
+title: 01 - intro
+description: intro
+course: ["aws solutions architect"]
+---
+
+Amazon Web Services (AWS) is a cloud provider.
+
+It provides servers and services on demand that can easily scale.
+
+It's in direct contrast to on-premise solutions that require ordering servers and setting them up manually.
+
+It powers a lot of well known websites, like Netflix.com, which rents all their servers from AWS.
+
+## Facts
+
+- AWS has 90b in revenue
+- 31% of cloud market
+- 1m active users
+
+## Use cases
+
+Almost any use case you could want.
+
+- Hosting websites
+- Backups and storage
+- Big data analytics etc etc
diff --git a/content/notes/aws-solutions-architect/02.infra.md b/content/notes/aws-solutions-architect/02.infra.md
new file mode 100644
index 0000000..a611c3b
--- /dev/null
+++ b/content/notes/aws-solutions-architect/02.infra.md
@@ -0,0 +1,27 @@
+---
+title: 02 - infra
+description: infra
+course: ["aws solutions architect"]
+---
+
+You can check out the AWS infrastructurnd regions here: [https://aws.amazon.com/about-aws/global-infrastructure/regions_az/](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/)
+
+It's a global infrastructure, meaning we can deploy apps to be global
+
+## Regions
+
+Regions have names, e.g us-east-1
+
+A region is a cluster of data centres
+
+Most services are region scoped.
+
+Some factors in choosing regions could be:
+
+- Compliance
+- Latency
+- Service availability (some regions don't have certain services)
+
+Each region has 'zones', which are separate data centres from each other and are isolated from disasters.
+
+Not all services are available in all regions, see here: [https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/?p=ngi&loc=4](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/?p=ngi&loc=4)
diff --git a/content/notes/aws-solutions-architect/03.iam.md b/content/notes/aws-solutions-architect/03.iam.md
new file mode 100644
index 0000000..a5aaf5d
--- /dev/null
+++ b/content/notes/aws-solutions-architect/03.iam.md
@@ -0,0 +1,113 @@
+---
+title: 03 - iam
+course: ["aws solutions architect"]
+---
+
+## Identity Access Management (IAM)
+
+This is a global service.
+
+A root account is created first, but it shouldn't be used thereafter, we should create new users instead.
+
+Users can be grouped.
+
+Groups only contain users, not other groups.
+
+A user can be in multiple groups, and users don't _need_ to be in groups.
+
+## IAM permissions
+
+User permissions are defined as _policies_, which are JSON documents that manage user permissions.
+
+AWS follows the **least privilege principle**, meaning you don't give more permissions than a user needs.
+
+## Policy inheritence
+
+User inherit policies from the team(s) they belong to. But you can also create an **inline policy**, which is a policy just for one user, assuming they don't belong to a group.
+
+If a user belong to 2 groups, they will inherit the permissions from both groups.
+
+## IAM Policy
+
+Consist of:
+
+1. Version
+2. ID
+3. Statement
+
+Statements consist of ids, effects, principals, actions and resources.
+
+## Protecting your account
+
+There are two main ways to protect an account, a password policy and Multi Factor Authentication (MFA)
+
+### Password policy
+
+This policy defines what type of password users can store (character and length etc) but also how often the password resets, whether the user can use the same password more than once etc.
+
+### MFA
+
+This is an additional step during login and requires a user to type in an additional code once they have submitted their password. There are four ways to do this:
+
+1. A virtual device like Google Authenticator
+2. A security key (U2F) device
+3. Hardware key fob
+4. Hardware key fob (
+
+## Protecting your account
+
+There are two main ways to protect an account, a password policy and Multi Factor Authentication (MFA)
+
+### Password policy
+
+This policy defines what type of password users can store (character and length etc) but also how often the password resets, whether the user can use the same password more than once etc.
+
+### MFA
+
+This is an additional step during login and requires a user to type in an additional code once they have submitted their password. There are four ways to do this:
+
+1. A virtual device like Google Authenticator
+2. A security key (U2F) device
+3. Hardware key fob
+4. Hardware key fob (for AWS GovCloud US)
+
+## IAM roles for services
+
+Sometimes we need services to perform actions for us. For example we can have an EC2 instance and we need that instance to manage our AWS account.
+
+To do this we use IAM Roles.
+
+For example, we create a role that is trusted by a service, such as EC2. This role may have permissions like accessing `IAMReadOnlyAccess`. Now our EC2 instance can read our IAM.
+
+## Security tools
+
+### IAM Credentials Report
+
+This lists all users and their credentials.
+
+### Access Advisor
+
+This lists individual users and the permissions granted to them, including them they last accessed the services. This allows you to understand which services the users has access to and which you might want to revoke.
+
+## Best practices for IAM
+
+- Don't use root account except for setup
+- 1 physical users should = one AWS user
+- Assign users to groups and assign permissions to groups
+- Create strong pw policy
+- Use and enforce MFA
+- Create Roles for giving permissions to AWS services
+- Use access keys for programmatuc access
+- Audit permissions using Access Advisor
+- Never share IAM user & access keys
+
+## Overview
+
+- Users are physical users and each have their own access
+- Groups contain users only
+- Policies are JSON docs with permissions
+- Roles give features AWS access
+- Security: MFA + p/w policy
+- CLI/SDK are programmatic acesss
+- Access keys : give programmatic access
+- Audit users with credential reports and access advisor
diff --git a/content/notes/aws-solutions-architect/04.cli-sdk.md b/content/notes/aws-solutions-architect/04.cli-sdk.md
new file mode 100644
index 0000000..a0ce259
--- /dev/null
+++ b/content/notes/aws-solutions-architect/04.cli-sdk.md
@@ -0,0 +1,34 @@
+---
+title: 04 - cli sdk
+course: ["aws solutions architect"]
+---
+
+# AWS CLI, SDK and Cloud Shell
+
+There are 3 ways to access AWS:
+
+1. The web console
+2. AWS CLI
+3. AWS SDKs
+
+The CLI is open source and allows you to make operations from the command line.
+
+The SDKs allow you to access AWS programatically through API calls using a set of language specific APIs, including: JS, Node, PHP, Python, .NET, Ruby, Java, Go, C++.
+
+## AWS CLI
+
+You need to use an access token to setup the AWS CLI. You define access tokens per user and then user :
+
+`aws configure` to create access for that user from the command line.
+
+## Cloudshell
+
+You can use cloudshell in the AWS console as well.
+
+This will automatically use the active user's credentials. It allows for :
+
+- File downloads
+- Uploads
+- etc etc
+
+It is only available in some regions, however.
diff --git a/content/notes/aws-solutions-architect/10.ami.md b/content/notes/aws-solutions-architect/10.ami.md
new file mode 100644
index 0000000..9c66703
--- /dev/null
+++ b/content/notes/aws-solutions-architect/10.ami.md
@@ -0,0 +1,21 @@
+---
+title: 10 - ami
+course: ["aws solutions architect"]
+---
+
+# Amazon Machine Image (AMI)
+
+AMIs are customisations of an EC2 instance.
+
+It's basically a snapshot of an instance that can be used to launch new, customised EC2 instances. This helps with faster boot times.
+
+So far we have been launching using Amazon's own Linux AMIs. But we can:
+
+- Create our own AMIs
+- Used 3rd party vendor AMIs (can cost)
+
+This allows for the following:
+
+- Create an instance in one AZ
+- Create AMI from that instance
+- Launch a new instance from that AMI in another AZ
diff --git a/content/notes/aws-solutions-architect/11.ec2-instance-store.md b/content/notes/aws-solutions-architect/11.ec2-instance-store.md
new file mode 100644
index 0000000..4e6aab2
--- /dev/null
+++ b/content/notes/aws-solutions-architect/11.ec2-instance-store.md
@@ -0,0 +1,13 @@
+---
+title: 11 - ec2 instance-store
+course: ["aws solutions architect"]
+---
+
+Hardware disk attached to EC2 instance, _not_ a network drive, like EBS Volumes.
+
+> EC2 instance stores are higher performance storage for EC2 instance that are attached to the instance and aren't network drives
+
+- Better I/O performenace
+- EC2 instance termination will lose the disk (ephemeral)
+- Not for long term storage, instead use EBS
+- Backups are your responsibility
diff --git a/content/notes/aws-solutions-architect/12.scalability-availability.md b/content/notes/aws-solutions-architect/12.scalability-availability.md
new file mode 100644
index 0000000..497af13
--- /dev/null
+++ b/content/notes/aws-solutions-architect/12.scalability-availability.md
@@ -0,0 +1,36 @@
+---
+title: 12 - scalability availability
+course: ["aws solutions architect"]
+---
+
+## Vertical vs horizontal scalability
+
+### Vertical
+
+This means adding more power to existing instance. For example turning EC2 instance from a T2 to a T3 instance - so adding more compute.
+
+- There are hardware limits to how much this can scale
+- Common use case fo non-distributed systems like a database
+- RDS and ElastiCache can scale vertically
+
+> AWS terms: scale up / down
+
+### Horizontal
+
+Involves adding more instances for the application. Instead of increasing capacity of existing machines, we add new machines.
+
+So going from one machine to two machines for the same application is an example of horizontal scaling.
+
+- Common use case for web apps
+- Implies distributed systems
+
+> AWS terms: scale out / in
+
+## Availability
+
+This refers to application uptime. It usually involves running same application across multiple AZs so that if one data centre crashes, your app will be active in another so there is no downtime.
+
+AWS examples:
+
+- Auto Scaling Group multi AZ
+- Load Balancer multi AZ
diff --git a/content/notes/aws-solutions-architect/13.load-balancing-ELB.md b/content/notes/aws-solutions-architect/13.load-balancing-ELB.md
new file mode 100644
index 0000000..fe6b413
--- /dev/null
+++ b/content/notes/aws-solutions-architect/13.load-balancing-ELB.md
@@ -0,0 +1,191 @@
+---
+title: 13 - load balancing-ELB
+course: ["aws solutions architect"]
+---
+
+## Load balancing with Elastic Load Balancer (ELB)
+
+Load balancing refers to distributing user traffic to multiple instances.
+
+A load balancer sits in front of your instances and distibutes traffic depending on instance load.
+
+This diagram explains it well:
+
+![](/images/aws/loadbalancer.png)
+
+## Why?
+
+- Spreads load
+- Single point of access using DNS
+- Failure handling
+- Load balancers can check health of instances
+ - Load balancers check if downstream server will reply to requests
+- HTTPS support
+- High availability
+- Can separate public and private traffic
+
+## Elastic Load Balancer (ELB)
+
+Managed service that AWS maintains. No need to configure it yourself. Integrates with:
+
+- EC2, EC2 auto scaling groups, ECS
+- AWS Cert Manager, CloudWatch
+- Route 53, AWS WAF, AWS Global Accelerator
+
+### ELB types
+
+Different generation of EBS:
+
+1. Classic Load Balancer (old gen)
+2. Application Load Balancer (v2)
+3. Network Load Balancer (v2 new gen)
+4. Gateway Load Balancer - 2020 and newest
+
+Load balancers will allow access from anywhere, but the connection betweem EBS and EC2 instances is managed through security groups so EC2 access is restricted.
+
+## Load balancer types
+
+> It's important to note that ELB is the overall service, but there are different types of load balancers for different purposes, as noted below
+
+### Application Load Balancer (ALB)
+
+This load balancer works at the HTTP level and allows for:
+
+- HTTP load balancing
+- Container load balancing
+- HTTP/2 and WebSocket
+- Redirects (HTTP > HTTPS)
+
+Load balancing can be based on:
+
+- Path based. /users or /posts for e.g
+- Hostname (subdomains, for e.g)
+- Query strings and headers
+
+> ALB is good for microservices where you want to balance load between services and each service might hit a different endpoint, subdomain, container etc
+
+![](/images/aws/alb.png)
+
+## Target groups
+
+Path routing, for example, determines which target group (a set of resources) to route request to. Target groups can be:
+
+- EC2 instances
+- ECS tasks
+- Lambda functions
+- IP addresses
+
+For example query param target group routing:
+
+![](/images/aws/alb-query.png)
+
+# Good to know:
+
+- ALBs get a fixed hostname
+- App servers don't see client IP directly, instead it's passed through headers
+
+### Network Load Balancer (NLB)
+
+This is a lower level load balancer (Layer 4) - affecting the TCP and UDP layer.
+
+It has:
+
+- Ultra low latency
+- Handles millions of RPS
+- 1 static IP per AZ and supports supports Elastic ips
+
+### Gateway Load Balancer (GLB)
+
+- A way to scale and manage a fleet of 3rd party network applications in access
+- Example: firewalls, intrustion detection
+- It is a way to route all traffic through a gateway
+- You can think of it as an additional layer between traffic and your applications. It allows you to analyse network traffic and possibly drop
+- Operates at Layer 3 (network layer)
+- It has the following functions:
+ - Transparent Network Gatewau - single entry/exit for all traffic
+ - Load Balancer - distributes traffic to virtual applications
+- It uses GENEVE protocol on port 6081
+
+> GLB allows you to analyse all traffic and drop it if necessary.
+
+![application load balancer](/images/aws/glb-2.png)
+
+Target groups can be:
+
+- EC2 instances
+- IP addresses
+
+## Sticky Sessions
+
+Sticky sessions is a way to keep the same user on the same instance even when using a load balancer.
+
+Usually a load balancer will distribute traffic among different instances, but if you want to keep the same user on the same instance, for example to keep their session information, you can use sticky sessions.
+
+It's controlled using a cookie, which is either:
+
+- A custom cookie
+- A default cookie set by Sticky Sessions
+
+## Cross-Zone Load balancing
+
+Cross-zone load balancing allows you to distribute load balancer traffic across different AZs. It can be turned on and off per load balancer.
+
+![](/images/aws/czlb.png)
+
+If it's turned on, all traffic will be spread evenly across AZs. Otherwise it won't.
+
+It's automatically on for ALBs, but needs to be turned on manually for NLBs and GLBs and will incur a charge for the latter two.
+
+## Connection Draining
+
+This gives the request time to complete while instance is de-registering or "unhealthy".
+
+It will stop sending requests to instances that are de-registering.
+
+Can be set from 1 to 3600 seconds.
+
+## Auto Scaling Group (ASG)
+
+An ASG adds or remove more instances depending on user load. It's referred to as:
+
+- Scale out (adding EC2 instances)
+- Scale in (removing EC2 instances)
+
+They are free! You only pay for the infra you get. They also work OOTB with load balancers, which connect automatically to new instances added.
+
+You get to define:
+
+- **Minimum capacity**
+- **Desired capacity**
+- **Maximum capacity**
+
+You define a _launch template_ that contains all EC2 info that are used as parameters to launch new instances.
+
+### CloudWatch alarms
+
+When alarms are triggered (avg CPU or custom metric) the Auto Scaling Group can be enacted.
+
+### Auto Scaling Groups - Scaling Policies
+
+Scaling policies allow us to define under what conditions to scale.
+
+#### Dynamic Scaling
+
+- _Target Tracking Scaling_: define metric (for e.g CPU usage) and ASG will scale out or in to match that metric
+- _Step Scaling_: This scales depdning on CloudWatch alarm trigger, for e.g CPU > 70%, add 2 units
+- _Scheduled scaling_: Scale based on times. Based on usage patterns
+
+#### Predictive Scaling
+
+ASG analyses historical load, forecasts load and then scales
+
+## Good metrics to scale on
+
+- CPU utilisation
+- RequestCountPerTarget: make sure requests per instance is stable
+- Average network in/out
+- Any custom metric!
+
+## Scaling cooldown
+
+After removing/adding instances, which is a 5 minute cooldown where no instance changes can be made. Allows for instance stablisation
diff --git a/content/notes/aws-solutions-architect/14.ssl-tls.md b/content/notes/aws-solutions-architect/14.ssl-tls.md
new file mode 100644
index 0000000..033f662
--- /dev/null
+++ b/content/notes/aws-solutions-architect/14.ssl-tls.md
@@ -0,0 +1,38 @@
+---
+title: 14 - ssl tls
+course: ["aws solutions architect"]
+---
+
+## SSL / TLS Basics
+
+SSL refers to Secure Socket Layer and is a way to encrypt network traffic that is in flight.
+
+For AWS it refers to encrypting traffic between the client and the load balancer. Traffic between the load balancer and instances are handled over plain HTTP.
+
+The S in HTTPS refers to 'secure', which means the traffic is using SSL.
+
+Nowadays, traffic is encrypted using TLS (Transport Layer Security), which is a newer version of SSL, but people still refer to it as SSL.
+
+SSl certs are issued by a Certificate Authority (CA) and have an expiration date.
+
+![](/images/aws/ssl.png)
+
+## Load Balancer and SSL
+
+- LB uses an X.509 Certificate
+- ACM (AWS Certificate Manager) manages certs
+- You must set a default SSL cert
+- Clients can use SNI (Server Name Indication) to specify the hostname they reach
+
+## Server Name Indication (SNI)
+
+- This helps manage multiple SSLs on one Server
+- Client must indicate server hostname to get the right SSL
+
+![](/images/aws/sni.png)
+
+## Load Balancer Support
+
+- _CLB_: supports 1 SSL
+- _ALB_: supports multiple with SNI
+- _NLB_: support multiple with SNI
diff --git a/content/notes/aws-solutions-architect/15.RDS.md b/content/notes/aws-solutions-architect/15.RDS.md
new file mode 100644
index 0000000..11848fc
--- /dev/null
+++ b/content/notes/aws-solutions-architect/15.RDS.md
@@ -0,0 +1,186 @@
+---
+title: 15 - RDS
+course: ["aws solutions architect"]
+---
+
+# Relational Database Services (RDS)
+
+- RDS is a managed database service from AWS
+- It uses SQL and supports the following databases:
+
+1. Postgres
+2. MySQL
+3. MariaDB
+4. Oracle
+5. Microsoft SQL server
+6. IBM DB2
+7. Aurora (AWS proprietary service)
+
+## Why use a managed service?
+
+AWS manages the whole service for you, including deployment of databases onto infrastructure. Because it's managed, you don't have access to the underlying infra, so you can't SSH into the DB servers.
+
+Advantages of this:
+
+- Automated provisioning, OS patches
+- Backups
+- Monitoring dashboards
+- Scaling (horizontal and vertical)
+- Multi AZ (recovery)
+- Read replicas (performance)
+
+## Storage Auto Scaling
+
+AWS can scale DB stores automatically depending on usage. For e.g when you are running out of DB space.
+
+You have to set a **Maximum Storage Threshold**.
+
+> Auto scaling is good for apps with unpredictable DB operations## Storage Auto Scaling
+
+## Read Replicas VS Multi AZ
+
+### Read Replicas
+
+Read replicas are DB replicas of the main DB that allow for more read operations (scalability).
+
+They work by replicating the main DB and then allowing read operations to those replicas.
+
+The data between the main DB and replica DBs is _eventually consistent_, meaning that they will eventually have identical data, but there is a chance a read operation to a replica will receive back outdated data.
+
+Replicas can also be promoted to their own DB.
+
+![RDS replicas](/assets/rds-replicas.png)
+
+#### Network Costs for replicas
+
+There are network costs associated with replicas. If you replicate across a different AZ, a fee is incurred, else it's free.
+
+### Multi AZ
+
+Multi AZ RDS are DB instances that are on standby should something go wrong with the master DB.
+
+This helps increase availability
+
+> Multi AZ RDS is good for database availability
+
+There is just one DNS name needed and if there is network loss, instance of storage failure, read and write operations will be passed to the instance on standby:
+
+![Multi AZ RDS](/assets/multi-az-rds.png)
+
+**There is no downtime associated when creating Multi AZ - you just modify the DB**
+
+> Important to know that you can also use read replicas for Disaster Recovery (DS)
+
+## RDS Custom
+
+While it's been mentioned that RDS is a fully managed service, with two databases you do get OS access and can SSH into the instances:
+
+- Orcale
+- Microsoft SQL server
+
+For these two DBs you can:
+
+- Configure settings
+- Install patchesEnable features
+- Access EC2 instance using SSH
+
+> RDS is a managed services, except for Oracle and MS SQL, which allow customisation and EC2 access
+
+## Amazon Aurora
+
+This is Amazon's proprietary database offering, it's not open source. It:
+
+- Aurora works with bothPostgres and MySql.
+- Has 5x better perf than MySQL and 3x the perf of Postgres
+- Storage automatically grows in 10GB increments, up to 128TB
+- Can have 15 read replicas
+- Failover is instantaneous
+
+### Aurora availability and read scaling
+
+Aurora created 6 copies of data across 3 AZs (diagram).
+
+The storage is self healing and auto expanding.
+
+One instance takes writes (the master) and the data is replicated across the instances, which can be used for read operations.
+
+![](/assets/aurora-scaling.png)
+
+## Aurora Cluster
+
+Aurora has a:
+
+1. Shared volume
+2. 1 master writer - one endpoint
+3. 5 readers - one endpoint that scale automatically
+
+![](/assets/aurora-cluster.png)
+
+## Advanced - Aurora replicas and auto scaling
+
+If read endpoints receive much more traffic then the read replicas will autoscale:
+
+![](/assets/aurora-read-scaling.png)
+
+## Avanced - replicas and custom endpoints
+
+By default, replicas share the same endpoint, but you can specify a custom endpoint should some of your replicas be unique, for example the DBs are larger or more powerful:
+
+![](/assets/aurora-custom-endpoint.png)
+
+## Advanced - Aurora serverless
+
+Serverless option from AWS that:
+
+- automatically scales depending on usage
+- no storage planning needed
+- pay per second pricing model
+
+## Advanced - global availability
+
+For global availability you can:
+
+1. Create Aurora cross region availability using read replicas
+2. Use an Aurora global database (recommended):
+
+The global database has:
+
+- 1 primary region
+- up to 5 additional read only regions
+- 16 read replicas in the regions
+- helps decrease latency globally
+- replication takes less than 1s
+
+> In the exam when "cross region replication < 1s" is mentioned, it's referring to Aurora Global
+
+## Advanced - Aurora Machine Learning
+
+SQL integration with other AWS ML tools: SageMaker and Comprehend. It basically takes data from your Aurora tables and uses them to power ML tools. The data will be routed through Aurora:
+
+![](/assets/aurora-ml.png)
+
+## RDS Backups
+
+There are two ways to backup:
+
+1. Automated Backups: daily full backups, can restore from any point up to 5 min before backup (so any time in the past up until 5min ago). 1-35 days retention
+2. Manual DB Snapshots: manually triggered and retention as long as you want
+
+> To save money, instead of stopping a DB (you will still pay) you can create a snapshot and restore from there in the future
+
+## Aurora backups
+
+It's similar, with automated backups and manual DB snapshots. Automated backups cannot be turned off!
+
+## Restoring operations
+
+Two options:
+
+1. Restoring automatically from backup/snapshot. This will create a new DB.
+2. Restore MYSQL / Aurora from S3. Involves creating a backup on S3 and then restoring from there. For Aurora you have to use Percona XtraBackup for this.
+
+## Aurora DB cloning
+
+Creates new Aurora cluster from an existing one. You can do this to run DB testing, for example.
+
+When the new cluster is created and new write operations are made to it, new storage is allocated only to the new DB cluster.
diff --git a/content/notes/aws-solutions-architect/5.ec2.md b/content/notes/aws-solutions-architect/5.ec2.md
new file mode 100644
index 0000000..b0d8f31
--- /dev/null
+++ b/content/notes/aws-solutions-architect/5.ec2.md
@@ -0,0 +1,127 @@
+---
+title: 05 - ec2
+course: ["aws solutions architect"]
+---
+
+# EC2
+
+An EC2 instance is just a virtual machine that you hire. It stands for **Elastic Compute Cloud** and is infrastructure as a service.
+
+You can also:
+
+- Store data on virtual drives
+- Distribute load across machines
+- Scale services
+
+You can choose what you want your virtual machine to be and its power, including CPU, RAM, networking capabilities etc. You can also choose between Mac, Linux and Windows machines.
+
+## Bootstrapping
+
+What the machine does at launch can be controlled using bootstrapping scripts
+
+## EC2 instance types
+
+https://aws.amazon.com/ec2/instance-types/
+https://instances.vantage.sh/
+There are different types of EC2 instances, designed for different purposes. You can find them above.
+
+Using _m5.2xlarge_ as an example, the naming convention is:
+
+- **m**: instance class, in this case m means general purpose
+- **5**: generation (AWS improves gens over time)
+- **2xlarge**: size, so the CPU and processing power
+
+### General purpose
+
+### Compute optimized
+
+High performance with good CPU. Examples are of the **C** name
+
+### Memory optimized
+
+High RAM. High performance for databases, cache stores, big unstructured data. An example are the **R** instances
+
+### Storage optimised
+
+Good for high, sequential read and write access to large data sets.
+
+Examples:
+
+- Databases
+- Cache for in memory dbs
+- Online transactioning systems
+- Distributed filed systems
+
+## EC2 instance firewalls
+
+You can control who can access the EC2 instance and how your EC2 instance interacts with the internet using security groups.
+
+### Security groups
+
+Security groups contain _allow_ rules that can reference IPs or groups that can access instances. Therefore they act as a firewall on EC2 instance, by regulating:
+
+- Port access
+- Authorised IP ranges
+- Inbound traffic
+- Outbound traffic
+
+![security groups](/images/aws/securitygroups.png)
+
+Groups:
+
+- Can be attached to multiple instances
+- Locked down to region/VPC combination
+- Live outside Ec2 instances - they are their own standalone thing
+- timeouts usually mean security group issue
+- inbound traffic is blocked by default
+- outbound traffic is authorised by default
+- you can attach security groups to more than one instance
+
+## Ports
+
+These are the ports you must know:
+
+![ports](/images/aws/ports.png)
+
+## SSH
+
+SSH is a CLI that can be used on Mac and Linux and Windows V > 10 (or PuTTy) below V10
+
+EC2 instance connect also allows connection to your EC2 instances.
+
+AWS gives you the user EC2-user already, so the SSH command to login to the server has the following components:
+
+1. ssh ec2-user@<YOUR-PUIBLIC-IP-ADDRESS>
+2. you need to use the .pem file (which contains a private key) using the -i flag (identity file flag)
+
+The full command: `ssh -i EC2Tutorial.pem ec2-user@44.201.88.145`
+
+> With all EC2 instances, if you experience a timeout, either when using SSH or otherwise, it's usually a security group issue
+
+## EC2 instance connect
+
+You can do all this in the browser, without managing keys by going to SSH Instance Connect.
+
+## IAM Roles for EC2 instances
+
+You should always manage EC2 instance access through IAM roles, not by adding your credentials directly into the instance using `aws configure` as this data can be accessed by other users on the instance. So instead, attach an IAM Role to the EC2 instance and manage service access through role policies.
+
+## EC2 pricing
+
+There is different EC2 pricing, which you can see below depending on what's needed:
+
+[ex2pricing](/images/aws/ec2-pricing.png)
+
+## IPV4 vs 6
+
+AWS will charge for IPV6 ip addresses that go over 750 hours a month. So if you have more than 1 ip address it's likely you will incur costs.
+
+## EC2 controls
+
+There are a few ways to control the instances:
+
+- **Stop**: data is kept intact for next start. OS has to boot and can take time
+- **Terminate**: data and setup is lost
+- **Hibernate**: RAM is preserved, OS is not stopped / restarted. RAM state is written to file in volume.
+
+> Hibernation helps for saving RAM state, boot up fast and want long running processes. Can be no longer than 60 days.
diff --git a/content/notes/aws-solutions-architect/6.ips.md b/content/notes/aws-solutions-architect/6.ips.md
new file mode 100644
index 0000000..8ac58b7
--- /dev/null
+++ b/content/notes/aws-solutions-architect/6.ips.md
@@ -0,0 +1,26 @@
+---
+title: 06 - ips
+course: ["aws solutions architect"]
+---
+
+# IPs
+
+## IPV4 vs IPV6
+
+IPV4 are "classic" IP addresses, but they are running out, so the internet is moving to IPV6.
+
+The IPV4 address we get with our EC2 instance will be enough.
+
+The format is as follows: [0-255].[0-255].[0-255].[0-255] allowing for 3.6b IPV4 addresses
+
+## Private VS public IPs
+
+Public IPs allow servers to be accessed via the internet. Whereas private IPs can only be accessed internally from the same network. Private networks can interact with the WWW using an internet gateway (proxy)
+
+Private IPs can repeat, whereas public IPs cannot.
+
+## Elastic IPs
+
+AWS allows Elastic IPs, which are static IP addresses you can keep and then port between services. So the IP remains static and can be applied to different services. AWS gives you 5 of these. Each can only be attached to an instance one at a time.
+
+> Avoid elastic IPs, instead register a DNS name to a random public IP. Or use a load balancer.
diff --git a/content/notes/aws-solutions-architect/7.ec2-placement-groups.md b/content/notes/aws-solutions-architect/7.ec2-placement-groups.md
new file mode 100644
index 0000000..85f4f55
--- /dev/null
+++ b/content/notes/aws-solutions-architect/7.ec2-placement-groups.md
@@ -0,0 +1,16 @@
+---
+title: 07 - ec2 placement-groups
+course: ["aws solutions architect"]
+---
+
+# EC2 Placement Groups
+
+Placement groups allow you to define where your EC2 instances are deployed on AWS infrastructure.
+
+A placement group is either:
+
+1. _A cluster_: puts your instances in a low latency group in the same AZ. This helps with networking as instances are close to one another. Drawback is if the AZ fails, they all fail. **use case**: Good for big data jobs, apps that need low latency between instances.
+2. _Spread_: Think of this as opposite to clusters. Each instance is on different hardware across different AZs. This means reduced failure risk. Limited to 7 AZ per placement group. **Use case**: maximum high availability.
+3. _Partition_: spreads instances across different partitions within an AZ. Each partition represents a rack in AWS. Instances are distributed across different hardware racks and AZs in same region. **use cases**: Big data application, which are petition aware.
+
+![placement groups](/images/aws/placement-groups.png)
diff --git a/content/notes/aws-solutions-architect/8.eni.md b/content/notes/aws-solutions-architect/8.eni.md
new file mode 100644
index 0000000..eca1a88
--- /dev/null
+++ b/content/notes/aws-solutions-architect/8.eni.md
@@ -0,0 +1,27 @@
+---
+title: 08 - eni
+course: ["aws solutions architect"]
+---
+
+# Elastic Netowrk Interfaces (ENI)
+
+https://aws.amazon.com/blogs/aws/new-elastic-network-interfaces-in-the-virtual-private-cloud/
+
+ENIs are a virtual network card that give EC2 instances network access.
+
+Each ENI can have:
+
+1. a private iPV4 (or more)
+2. one elastic IPv4
+3. one public IPv4
+4. security groups
+5. a MAC address
+
+They are AZ bound and can be moved to other EC2 instances.
+
+> Network cards are a hardware component that allow computers to connect to networks
+> ENIs are outside of the EC2 instance, although they affect them they just aren't shared by the instance
+
+Each EC2 instance has one network interface.
+
+Moving an ENI between instances allows for quick failover.
diff --git a/content/notes/aws-solutions-architect/9.ec2-storage.md b/content/notes/aws-solutions-architect/9.ec2-storage.md
new file mode 100644
index 0000000..9ef11b7
--- /dev/null
+++ b/content/notes/aws-solutions-architect/9.ec2-storage.md
@@ -0,0 +1,102 @@
+---
+title: 09 - ec2 storage
+course: ["aws solutions architect"]
+---
+
+# EC2 storage options
+
+Storage options for EC2 instances
+
+## EBS Volume
+
+Elastic Block Store - network drive. Allows for data persistence.
+
+Can only be mounted to 1 instance at a time. It's bound to AZ. But you can attach more than one volume to an instance
+
+> Like a network USB stick. It's not a physical drive, it uses the network, so there is latency.
+
+You can move it, but you've gotta snapshot the volume.
+
+You can decide to terminate the EBS volume on instance termination
+
+## EBS Snapshot
+
+- You can snapshot your volumes to act as a backup.
+- Recommended to detach volume
+- Can copy across AZs - allowing volume restoration in another AZ
+- Copying to archive tier is much cheaper, but restoration is longer
+- Snapshot deletions can be recovered from a recycle bin
+- Fast Snapshot Restore (FSR) helps with latency on restoration
+- Can also create other volumes from existing snapshots, which can be in a different AZ
+
+## EBS Volume Types
+
+There are 6 types of EBS volume types
+
+- GP2 / GP3 (SSD): perf/price balance
+- io1 / io2 Block Express (SSD): High perf for low latency and high throughput
+- st 1 (HDD): Low cost, frequent access, throughput intensive workloads
+- sc 1 (HDD): Lowest cost for less frequent workloads
+
+Characterised by SIZE | THROUGHPUT | IOPS (I/O Ops per sec)
+
+> Only GP2/GP3 and io1 / io2 can be used at boot volumes
+
+### General purpose SSD (gp2/gp3)
+
+- Cost effective, low latency
+- System boots
+- 1GiB - 16 TiB
+- Gp3: 2k - 16k IOPS & 125 MiB/s - 1k MiB/s
+- Gp2: IOPS up to 16k. volume size and IOPS are linked
+
+### Provisioned IOPS volumes (io1/1o2)
+
+- Critical business apps
+- Apps that nmeed more than 16k IOPS
+- Great for DB workloads
+- io1: 4gb - 16tb max 64k IOPS
+- io2: 4gb - 64gb - max 256k IOPS
+
+### HDD (st1/sc1)
+
+- Can't be boot volumes
+- 125gb - 16tb
+- Throughpout optimised (st 1) - good for big data
+- Infrequent access (low cost) - sc 1
+
+Best resource: https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html
+
+### EBS multi attach
+
+For io1/2 families you can attach same EBS volume to multiple EC2 instances in the same AZ.
+
+This is good for high availability and concurrent writes.
+
+> Only good for one AZ and 16 EC2 instances at the same time
+
+## EBS Encryption
+
+- Data encrypted at rest
+- In flight encryption
+- Snapshot encryption
+- Uses KMs (AES-256).
+
+## Elastic file system (EFS)
+
+Allows for a network file system that allows multiple AZ instances to connect to the EFS through a security group.
+
+![efs](/images/aws/efs.png)
+
+Use cases: content management, web serving, data sharing, Wordpress. Only Linux compatible (POSIX) system and scales automatically.
+
+It has different perf modes:
+
+- General purpose
+- Throughput mode for Max I/O
+
+And storage classes:
+
+- standard
+- infrequent access
+- archive
diff --git a/content/notes/index.njk b/content/notes/index.njk
new file mode 100644
index 0000000..3dabc96
--- /dev/null
+++ b/content/notes/index.njk
@@ -0,0 +1,40 @@
+---
+layout: /layouts/layout.njk
+title: All notes
+description: A list of all notes
+---
+{% set coursesArr = [] %}
+
+{% for item in collections.coursesList %}
+ {% if coursesArr.indexOf(item.data.course) == -1 %}
+ {% set coursesArr = (coursesArr.push( item.data.course ), coursesArr) %}
+ {% endif %}
+{% endfor %}
+
+<div class="container">
+
+<ul class="list-disc pl-3">
+{% for theCourse in coursesArr %}
+ <li class="mb-3">
+ <input type="checkbox" class="hidden peer" id="checkbox-{{ loop.index }}">
+ <label for="checkbox-{{ loop.index }}">
+ <h2 class="mb-2 cursor-pointer font-heading text-xl text-teal-900 mb-2 capitalize">{{ theCourse }} <span>⏵</span></h2>
+ </label>
+ {% for item in collections.coursesList %}
+ {% if item.data.course == theCourse %}
+ <ul class="hidden peer-checked:block">
+ <li>
+ <h3 class="font-body font-light">
+ <a href="{{ item.url }}" class="text-[18px] mr-2 visited:text-red-600 no-underline">
+ {{ item.data.title }}
+ </a>
+ </h3>
+ </li>
+ </ul>
+ {% endif %}
+ {% endfor %}
+ </li>
+{% endfor %}
+</ul>
+
+</div>
diff --git a/content/projects/indexcooking.md b/content/projects/indexcooking.md
new file mode 100644
index 0000000..290d1d9
--- /dev/null
+++ b/content/projects/indexcooking.md
@@ -0,0 +1,10 @@
++++
+title = "index.cooking"
+description = "personal recipe db without the bloat"
+tags = [
+ "flask",
+ "mongodb",
+]
+[params]
+ link = "https://pubs.jezl.xyz"
++++
diff --git a/content/projects/polypubs.md b/content/projects/polypubs.md
new file mode 100644
index 0000000..49b34f5
--- /dev/null
+++ b/content/projects/polypubs.md
@@ -0,0 +1,8 @@
++++
+title = "polypubs"
+description = "find London pubs with polygons"
+tags = [
+ "maps",
+ "algolia",
+]
++++
diff --git a/content/snippets/11ty-fetch-put-request.md b/content/snippets/11ty-fetch-put-request.md
new file mode 100644
index 0000000..ac0a9e8
--- /dev/null
+++ b/content/snippets/11ty-fetch-put-request.md
@@ -0,0 +1,50 @@
+---
+title: Make a PUT request with @11ty/eleventy-fetch
+description: Make a PUT request with @11ty/eleventy-fetch
+tags: ["11ty"]
+---
+
+[11ty](https://www.11ty.dev/) fetch lets you cache resources for any remote asset at build time. It scored it in a cache in your project.
+
+The basic request format supports `GET` only:
+
+```js
+const EleventyFetch = require("@11ty/eleventy-fetch");
+
+module.exports = async function () {
+ let url = "https://api.github.com/repos/11ty/eleventy";
+
+ /* This returns a promise */
+ return EleventyFetch(url, {
+ duration: "1d", // save for 1 day
+ type: "json", // we’ll parse JSON for you
+ });
+};
+```
+
+You can update it to change it to a `PUT` request (there were no examples I could find of this anywhere). Here i'm making a PUT request to retrieve my username from [Hardcover](https://hardcover.app/) using a GraphQL query.
+
+```js
+const url = "https://api.hardcover.app/v1/graphql";
+
+return EleventyFetch(url, {
+ duration: "1d",
+ type: "json",
+ fetchOptions: {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ authorization: "<BEARER_TOKEN>",
+ },
+ body: JSON.stringify({
+ query: `{
+ me {
+ username
+ }
+ } `,
+ }),
+ },
+});
+```
+
+Running this at build time in `_data/username.json` will store the asset in a global `username` object.
diff --git a/content/snippets/algolia-copy-between-apps.md b/content/snippets/algolia-copy-between-apps.md
new file mode 100644
index 0000000..8bd80d8
--- /dev/null
+++ b/content/snippets/algolia-copy-between-apps.md
@@ -0,0 +1,27 @@
+---
+title: Copy indices between Algolia apps w/ CLI
+description: Copy indices between Algolia apps w/ CLI
+tags: ["algolia"]
+---
+
+Ensure the [Algolia CLI](https://www.algolia.com/doc/tools/cli/get-started/overview/#install-the-algolia-cli) is installed
+
+```console
+algolia profile add
+```
+
+Add both your profiles, e.g `account1`, `account`
+
+```console
+algolia objects browse INDEX_NAME > records.json --profile account1
+```
+
+Pull the records locally.
+
+Then upload to second app and index (ensure you change the `--profile` flag ).
+
+The `-F` (short for `--file`) flag in Algolia CLI lets you read from a JSON file.
+
+```console
+algolia objects import INDEX_2_NAME -F records.json --profile account2
+```
diff --git a/content/snippets/batch-compress-images-magick.md b/content/snippets/batch-compress-images-magick.md
new file mode 100644
index 0000000..e484390
--- /dev/null
+++ b/content/snippets/batch-compress-images-magick.md
@@ -0,0 +1,13 @@
+---
+title: Batch compress image using ImageMagick
+description: Batch compress image using ImageMagick
+tags: ["commandline"]
+---
+
+This command loops a directory (`cd` into it, i'm assuming you're in it) and for every `.jpg` it aims to compress it to 200kb (not guaranteed)
+
+```console
+for file in *.jpg; do magick "$file" -define jpeg:extent=200kb "compressed/$file" done;
+```
+
+Make a `compressed` directory first, of course.
diff --git a/content/snippets/batch-rename-files.md b/content/snippets/batch-rename-files.md
new file mode 100644
index 0000000..1413d11
--- /dev/null
+++ b/content/snippets/batch-rename-files.md
@@ -0,0 +1,11 @@
+---
+title: Batch rename files using rename
+description: Batch rename files using rename
+tags: ["commandline"]
+---
+
+This appends .json to each file that doesn't already have a .json extension.
+
+```console
+rename 's/$/.json/' *
+```
diff --git a/content/snippets/browse-update-algolia-index.md b/content/snippets/browse-update-algolia-index.md
new file mode 100644
index 0000000..6dbe91e
--- /dev/null
+++ b/content/snippets/browse-update-algolia-index.md
@@ -0,0 +1,53 @@
+---
+title: Browse Algolia index and update objects (JS)
+description: Browse Algolia index and update objects (JS)
+tags: ["algolia"]
+---
+
+```js
+const algolia = require("algoliasearch");
+const client = algolia("<YOUR-APP-ID>", "<YOUR-WRITE-API-KEY>");
+
+const index = client.initIndex("<YOUR-INDEX-NAME>");
+
+function transformFunction(hit) {
+ return {
+ ...hit,
+ foo: "bar",
+ };
+}
+
+async function transformObjects(batch, isDryRun) {
+ const newArr = batch.map((el) => {
+ return transformFunction(el);
+ });
+
+ // Dry run
+ if (isDryRun) {
+ console.log(newArr, "no objects were updated, this is a dry run");
+ }
+
+ // Real run
+ else {
+ try {
+ const data = await index.saveObjects(newArr);
+ const { objectIDs } = data;
+ console.log(
+ "The following objectIDs were successfully updated:",
+ objectIDs
+ );
+ } catch (err) {
+ console.log("an error occurred updating the records", err);
+ }
+ }
+}
+
+index
+ .browseObjects({
+ query: "",
+ batch: (batch) => {
+ transformObjects(batch, false);
+ },
+ })
+ .then(() => console.log("code completed"));
+```
diff --git a/content/snippets/jq-convert-jsonl-json.md b/content/snippets/jq-convert-jsonl-json.md
new file mode 100644
index 0000000..b58a021
--- /dev/null
+++ b/content/snippets/jq-convert-jsonl-json.md
@@ -0,0 +1,15 @@
+---
+title: Convert JSONL to JSON using JQ
+description: Convert JSONL to JSON using JQ
+tags: ["jq", "commandline"]
+---
+
+```console
+jq -s '.' input.jsonl > output.json
+```
+
+And the other way:
+
+```console
+jq -c '.[]' input.json > output.jsonl
+```
diff --git a/content/snippets/jq-count-json-items.md b/content/snippets/jq-count-json-items.md
new file mode 100644
index 0000000..1093bfb
--- /dev/null
+++ b/content/snippets/jq-count-json-items.md
@@ -0,0 +1,9 @@
+---
+title: Count items in JSON file using jq
+description: Count items in JSON file using jq
+tags: ["jq", "commandline"]
+---
+
+```console
+jq -s '. | length' sample_data.json
+```
diff --git a/content/snippets/loop-files-rename.md b/content/snippets/loop-files-rename.md
new file mode 100644
index 0000000..27a3134
--- /dev/null
+++ b/content/snippets/loop-files-rename.md
@@ -0,0 +1,13 @@
+---
+title: Batch rename files using file index
+description: Batch rename files using file index
+tags: ["commandline"]
+---
+
+This terminal command renames all .jpg files in a directory to `{name}{index}.jpg`:
+
+```console
+i=1; for f in *.jpg; do mv -- "$f" "name-$i.jpg"; i=$((i+1)); done
+```
+
+`name` is just a placeholder, replace with whatever
diff --git a/content/snippets/rip-edit-audio.md b/content/snippets/rip-edit-audio.md
new file mode 100644
index 0000000..0cbb35b
--- /dev/null
+++ b/content/snippets/rip-edit-audio.md
@@ -0,0 +1,33 @@
+---
+title: Ripping and cutting music sets
+description: Ripping and cutting music sets
+tags: ["commandline"]
+---
+
+Here's a simple process to rip audio, add metadata and cut the audio from the command line:
+
+```console
+yt-dlp --extract--audio --audio-quality 0 "https://youtu.be/vbkyazLGovM?si=SQXs6PHIcBrsr5to"
+```
+
+This rips a 3 hour set locally. Use `mv` if you want to rename the file. Let's call the file `set.m4a`.
+
+Adding metadata using `exiftool`:
+
+```console
+exiftool -Title="Big set" -Artist="Wicked skengman" set.m4a
+```
+
+It's a three hour set and we only want from 2:30:00 until the end, so only the last 30 minutes, for this we can use `ffmpeg`.
+
+```console
+ffmpeg -ss 2:30:00 -i set.m4a -c copy output.m4a
+```
+
+`copy` copies the file without re-encoding.
+
+If using `cmus` then run the following to update your library with the new file:
+
+```console
+:add ~/Music/
+```
diff --git a/content/snippets/tailwind-descendent-has.md b/content/snippets/tailwind-descendent-has.md
new file mode 100644
index 0000000..5134ac9
--- /dev/null
+++ b/content/snippets/tailwind-descendent-has.md
@@ -0,0 +1,19 @@
+---
+title: Apply a tailwind class based on descendent state
+description: Apply a tailwind class based on descendent state
+tags: ["tailwind"]
+---
+
+You can apply css classes to parent elements if any of the parent's descendents meet a certain condition using the `has()` selector.
+
+Extending this to Tailwind is easy. A use case I came against recently was this: we have a header with a class that's passed dynamically using React state. The class is `scroll-locked`, used to indicate that the mobile menu has been toggled and that whole app should be scroll locked, i.e `overflow: hidden`.
+
+Instead of prop drilling and passing state from the `<header>` to the `<body>`, which is where we want to scroll lock, we can use the `has()` selector on the `<body>` to apply a class only when the `<header className="scroll-locked">`.
+
+The css for this is:
+
+```jsx
+<body className="has-[header.scroll-locked]:truncate">
+```
+
+`has-*` is a Tailwind modifier and what we pass in the [square brackets] is just css selector. `:` is used as a separator between different parts of a utility class name.
diff --git a/hugo.toml b/hugo.toml
new file mode 100644
index 0000000..e51b2ff
--- /dev/null
+++ b/hugo.toml
@@ -0,0 +1,12 @@
+baseURL = 'https://jezl.xyz/'
+languageCode = 'en-gb'
+title = 'Jezl'
+[params]
+ favicon = "/images/favicon/favicon-32x32.png"
+[taxonomies]
+ course = "course"
+ tag = "tags"
+[markup]
+ [markup.goldmark]
+ [markup.goldmark.renderer]
+ unsafe = true \ No newline at end of file
diff --git a/layouts/404.html b/layouts/404.html
new file mode 100644
index 0000000..c6b3dfd
--- /dev/null
+++ b/layouts/404.html
@@ -0,0 +1,6 @@
+{{ define "title" }}404{{ end }}
+
+{{ define "main" }}
+<h1>404</h1>
+<h2>ʕノ•ᴥ•ʔノ ︵ ┻━┻</h2>
+{{ end }}
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
new file mode 100644
index 0000000..56818d7
--- /dev/null
+++ b/layouts/_default/baseof.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="{{ with .Site.LanguageCode }}{{ . }}{{ else }}en-US{{ end }}">
+ <head>
+ <meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett" />
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ {{- partial "favicon.html" . -}}
+ <title>
+ {{- block "title" . }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title
+ }}{{- end }}
+ </title>
+
+ {{- partial "seo_tags.html" . -}}
+ <meta name="referrer" content="no-referrer-when-downgrade" />
+ <link rel="stylesheet" href="/style.css" />
+ <link
+ href="https://unpkg.com/prismjs@1.20.0/themes/prism-okaidia.css"
+ rel="stylesheet"
+ />
+ {{ with .OutputFormats.Get "rss" -}} {{ printf `
+ <link rel="%s" type="%s" href="%s" title="%s" />
+ ` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }} {{ end -}} {{-
+ partial "custom_head.html" . -}}
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link
+ href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&family=Raleway:ital,wght@0,100..900;1,100..900&display=swap"
+ rel="stylesheet"
+ />
+ <script src="/index.js" defer></script>
+ </head>
+
+ <body>
+ <header>{{- partial "header.html" . -}}</header>
+ <main>{{- block "main" . }}{{- end }}</main>
+ <footer>{{- partial "footer.html" . -}}</footer>
+ </body>
+</html>
diff --git a/layouts/_default/list.html b/layouts/_default/list.html
new file mode 100644
index 0000000..49da071
--- /dev/null
+++ b/layouts/_default/list.html
@@ -0,0 +1,44 @@
+{{ define "main" }}
+<content>
+ {{ if .Data.Singular }}
+ <h3 style="margin-bottom:0">Filtering for "{{ .Title }}"</h3>
+ <small>
+ <a href="{{ "blog" | relURL }}">Remove filter</a>
+ </small>
+ {{ end }}
+ <ul class="blog-posts">
+ {{ range .Pages }}
+ <li>
+ <span>
+ <i>
+ <time datetime='{{ .Date.Format "2006-01-02" }}'>
+ {{ .Date.Format (default "02 Jan, 2006" .Site.Params.dateFormat) }}
+ </time>
+ </i>
+ </span>
+ <a href="{{ .Permalink }}">{{ .Title }}</a>
+ </li>
+ {{ else }}
+ <li>
+ No posts yet
+ </li>
+ {{ end }}
+ </ul>
+ <small>
+ <div>
+ {{ $currentSection := .Section }}
+ {{ range $tag, $taxonomy := .Site.Taxonomies.tags }}
+ {{ $count:= 0 }}
+ {{ range $taxonomy.Pages }}
+ {{ if eq .Section $currentSection }}
+ {{ $count = add $count 1 }}
+ {{ end }}
+ {{ end }}
+ {{ if gt $count 0 }}
+ <a href="{{ $taxonomy.Page.Permalink }}">#{{ $taxonomy.Page.Title }}</a>&nbsp;
+ {{ end }}
+ {{ end }}
+ </div>
+ </small>
+</content>
+{{ end }}
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
new file mode 100644
index 0000000..5d0d520
--- /dev/null
+++ b/layouts/_default/single.html
@@ -0,0 +1,17 @@
+{{ define "main" }} {{ if eq .Type "blog" }}{{ if not .Params.menu }}
+<h1>{{ .Title }}</h1>
+<p>
+ <i>
+ <time datetime='{{ .Date.Format "2006-01-02" }}'>
+ {{ .Date.Format (default "02 Jan, 2006" .Site.Params.dateFormat) }}
+ </time>
+ </i>
+</p>
+{{ end }}{{ end }}
+<content> {{ .Content }} </content>
+<p>
+ {{ range (.GetTerms "tags") }}
+ <a href="{{ .Permalink }}">#{{ lower .LinkTitle }}</a>
+ {{ end }}
+</p>
+{{ end }}
diff --git a/layouts/index.html b/layouts/index.html
new file mode 100644
index 0000000..9983b08
--- /dev/null
+++ b/layouts/index.html
@@ -0,0 +1,3 @@
+{{ define "main" }}
+{{ .Content }}
+{{ end }}
diff --git a/layouts/notes/list.html b/layouts/notes/list.html
new file mode 100644
index 0000000..b34d8b4
--- /dev/null
+++ b/layouts/notes/list.html
@@ -0,0 +1,12 @@
+{{ define "main" }}
+<content>
+ {{ range $course, $taxonomy := site.Taxonomies.course }}
+ <h2>{{ $course | title }}</h2>
+ <ul class="blog-posts">
+ {{ range $taxonomy.Pages }} {{ if eq .Type "notes" }}
+ <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
+ {{ end }} {{ end }}
+ </ul>
+ {{ end }}
+</content>
+{{ end }}
diff --git a/layouts/partials/custom_body.html b/layouts/partials/custom_body.html
new file mode 100644
index 0000000..951fb15
--- /dev/null
+++ b/layouts/partials/custom_body.html
@@ -0,0 +1,3 @@
+ <!-- A partial to be overwritten by the user.
+ Simply place a custom_body.html into
+ your local /layouts/partials-directory -->
diff --git a/layouts/partials/custom_head.html b/layouts/partials/custom_head.html
new file mode 100644
index 0000000..4c53c40
--- /dev/null
+++ b/layouts/partials/custom_head.html
@@ -0,0 +1,3 @@
+<!-- A partial to be overwritten by the user.
+ Simply place a custom_head.html into
+ your local /layouts/partials-directory -->
diff --git a/layouts/partials/favicon.html b/layouts/partials/favicon.html
new file mode 100644
index 0000000..ccf1a5d
--- /dev/null
+++ b/layouts/partials/favicon.html
@@ -0,0 +1,2 @@
+{{ with .Site.Params.favicon }}
+<link rel="shortcut icon" href="{{ . | absURL }}" />{{ end }}
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
new file mode 100644
index 0000000..861b2cc
--- /dev/null
+++ b/layouts/partials/footer.html
@@ -0,0 +1 @@
+<p>Find me here: <a href="https://git.jezl.xyz">git</a></p>
diff --git a/layouts/partials/header.html b/layouts/partials/header.html
new file mode 100644
index 0000000..12fe8b2
--- /dev/null
+++ b/layouts/partials/header.html
@@ -0,0 +1,7 @@
+<div>
+ <img src="/images/avatar/avatar.jpg" alt="avatar ">
+ <a href="{{ "" | relURL }}" class="title">
+ <h2>{{ .Site.Title }}</h2>
+ </a>
+ <nav>{{- partial "nav.html" . -}}</nav>
+</div>
diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html
new file mode 100644
index 0000000..be5539c
--- /dev/null
+++ b/layouts/partials/nav.html
@@ -0,0 +1,9 @@
+{{ with .Site.GetPage "/blog" }}
+<a href="{{ "blog" | relURL }}">blog</a>
+{{ end }}
+{{ with .Site.GetPage "/snippets" }}
+<a href="{{ "snippets" | relURL }}">snippets</a>
+{{ end }}
+{{ with .Site.GetPage "/projects" }}
+<a href="{{ "projects" | relURL }}">projects</a>
+{{ end }} \ No newline at end of file
diff --git a/layouts/partials/seo_tags.html b/layouts/partials/seo_tags.html
new file mode 100644
index 0000000..7cea9a1
--- /dev/null
+++ b/layouts/partials/seo_tags.html
@@ -0,0 +1,22 @@
+<!-- Primary Meta Tags -->
+<meta
+ name="title"
+ content="{{ with .Title }}{{ . }}{{ else }}{{ .Site.Title }}{{ end }}"
+/>
+<meta
+ name="description"
+ content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}"
+/>
+<meta name="keywords" content="{{ if .IsPage}}{{ range $index, $tag :=
+.Params.tags }}{{ $tag }},{{ end }}{{ else }}{{ range $plural, $terms :=
+.Site.Taxonomies }}{{ range $term, $val := $terms }}{{ printf "%s," $term }}{{
+end }}{{ end }}{{ end }}" />
+
+<!-- Open Graph / Facebook -->
+{{ template "_internal/opengraph.html" . }}
+
+<!-- Twitter -->
+{{ template "_internal/twitter_cards.html" . }}
+
+<!-- Microdata -->
+{{ template "_internal/schema.html" . }}
diff --git a/layouts/projects/list.html b/layouts/projects/list.html
new file mode 100644
index 0000000..1f9fc51
--- /dev/null
+++ b/layouts/projects/list.html
@@ -0,0 +1,40 @@
+{{ define "main" }}
+<content>
+ {{ if .Data.Singular }}
+ <h3 style="margin-bottom:0">Filtering for "{{ .Title }}"</h3>
+ <small>
+ <a href="{{ "blog" | relURL }}">Remove filter</a>
+ </small>
+ {{ end }}
+ <ul class="blog-posts">
+ {{ range .Pages }}
+ <li>
+ <a href="{{ .Params.Link }}">
+ {{ .Title }} - &nbsp;
+ </a>
+ <i> {{ .Description }}</i>
+ </li>
+ {{ else }}
+ <li>
+ No posts yet
+ </li>
+ {{ end }}
+ </ul>
+ <small>
+ <div>
+ {{ $currentSection := .Section }}
+ {{ range $tag, $taxonomy := .Site.Taxonomies.tags }}
+ {{ $count:= 0 }}
+ {{ range $taxonomy.Pages }}
+ {{ if eq .Section $currentSection }}
+ {{ $count = add $count 1 }}
+ {{ end }}
+ {{ end }}
+ {{ if gt $count 0 }}
+ <a href="{{ $taxonomy.Page.Permalink }}">#{{ $taxonomy.Page.Title }}</a>&nbsp;
+ {{ end }}
+ {{ end }}
+ </div>
+ </small>
+</content>
+{{ end }}
diff --git a/layouts/robots.txt b/layouts/robots.txt
new file mode 100644
index 0000000..0326f5c
--- /dev/null
+++ b/layouts/robots.txt
@@ -0,0 +1,2 @@
+User-Agent: *
+Sitemap: {{ "sitemap.xml" | absURL }}
diff --git a/layouts/snippets/list.html b/layouts/snippets/list.html
new file mode 100644
index 0000000..2dc6cd4
--- /dev/null
+++ b/layouts/snippets/list.html
@@ -0,0 +1,40 @@
+{{ define "main" }}
+<content>
+ {{ if .Data.Singular }}
+ <h3 style="margin-bottom:0">Filtering for "{{ .Title }}"</h3>ijkdwnwjka
+ <small>
+ <a href="{{ "blog" | relURL }}">Remove filter</a>
+ </small>
+ {{ end }}
+ <ul class="blog-posts">
+ {{ range .Pages }}
+ <li>
+ <a href="{{ .Permalink }}">
+
+ {{ .Title }}
+ </a>
+ </li>
+ {{ else }}
+ <li>
+ No posts yet
+ </li>
+ {{ end }}
+ </ul>
+ <small>
+ <div>
+ {{ $currentSection := .Section }}
+ {{ range $tag, $taxonomy := .Site.Taxonomies.tags }}
+ {{ $count:= 0 }}
+ {{ range $taxonomy.Pages }}
+ {{ if eq .Section $currentSection }}
+ {{ $count = add $count 1 }}
+ {{ end }}
+ {{ end }}
+ {{ if gt $count 0 }}
+ <a href="{{ $taxonomy.Page.Permalink }}">#{{ $taxonomy.Page.Title }}</a>&nbsp;
+ {{ end }}
+ {{ end }}
+ </div>
+ </small>
+</content>
+{{ end }}
diff --git a/static/images/avatar/avatar.jpg b/static/images/avatar/avatar.jpg
new file mode 100644
index 0000000..8c4545a
--- /dev/null
+++ b/static/images/avatar/avatar.jpg
Binary files differ
diff --git a/static/images/aws/2025-02-10-19-51-19.png b/static/images/aws/2025-02-10-19-51-19.png
new file mode 100644
index 0000000..118c29c
--- /dev/null
+++ b/static/images/aws/2025-02-10-19-51-19.png
Binary files differ
diff --git a/static/images/aws/alb-query.png b/static/images/aws/alb-query.png
new file mode 100644
index 0000000..ef9631d
--- /dev/null
+++ b/static/images/aws/alb-query.png
Binary files differ
diff --git a/static/images/aws/alb.png b/static/images/aws/alb.png
new file mode 100644
index 0000000..d7236e6
--- /dev/null
+++ b/static/images/aws/alb.png
Binary files differ
diff --git a/static/images/aws/aurora-cluster.png b/static/images/aws/aurora-cluster.png
new file mode 100644
index 0000000..0f436a7
--- /dev/null
+++ b/static/images/aws/aurora-cluster.png
Binary files differ
diff --git a/static/images/aws/aurora-custom-endpoint.png b/static/images/aws/aurora-custom-endpoint.png
new file mode 100644
index 0000000..5203d5d
--- /dev/null
+++ b/static/images/aws/aurora-custom-endpoint.png
Binary files differ
diff --git a/static/images/aws/aurora-ml.png b/static/images/aws/aurora-ml.png
new file mode 100644
index 0000000..2d08d5b
--- /dev/null
+++ b/static/images/aws/aurora-ml.png
Binary files differ
diff --git a/static/images/aws/aurora-read-scaling.png b/static/images/aws/aurora-read-scaling.png
new file mode 100644
index 0000000..33bff97
--- /dev/null
+++ b/static/images/aws/aurora-read-scaling.png
Binary files differ
diff --git a/static/images/aws/aurora-scaling.png b/static/images/aws/aurora-scaling.png
new file mode 100644
index 0000000..37beefd
--- /dev/null
+++ b/static/images/aws/aurora-scaling.png
Binary files differ
diff --git a/static/images/aws/czlb.png b/static/images/aws/czlb.png
new file mode 100644
index 0000000..098cf2d
--- /dev/null
+++ b/static/images/aws/czlb.png
Binary files differ
diff --git a/static/images/aws/glb-2.png b/static/images/aws/glb-2.png
new file mode 100644
index 0000000..7aad11e
--- /dev/null
+++ b/static/images/aws/glb-2.png
Binary files differ
diff --git a/static/images/aws/glb.png b/static/images/aws/glb.png
new file mode 100644
index 0000000..e7365fd
--- /dev/null
+++ b/static/images/aws/glb.png
Binary files differ
diff --git a/static/images/aws/loadbalancer.png b/static/images/aws/loadbalancer.png
new file mode 100644
index 0000000..d732ba5
--- /dev/null
+++ b/static/images/aws/loadbalancer.png
Binary files differ
diff --git a/static/images/aws/moss.png b/static/images/aws/moss.png
new file mode 100644
index 0000000..c299827
--- /dev/null
+++ b/static/images/aws/moss.png
Binary files differ
diff --git a/static/images/aws/multi-az-rds.png b/static/images/aws/multi-az-rds.png
new file mode 100644
index 0000000..bffe7ae
--- /dev/null
+++ b/static/images/aws/multi-az-rds.png
Binary files differ
diff --git a/static/images/aws/nlb.png b/static/images/aws/nlb.png
new file mode 100644
index 0000000..8f733cf
--- /dev/null
+++ b/static/images/aws/nlb.png
Binary files differ
diff --git a/static/images/aws/rds-replicas.png b/static/images/aws/rds-replicas.png
new file mode 100644
index 0000000..ec98cae
--- /dev/null
+++ b/static/images/aws/rds-replicas.png
Binary files differ
diff --git a/static/images/aws/sni.png b/static/images/aws/sni.png
new file mode 100644
index 0000000..a7f2ed6
--- /dev/null
+++ b/static/images/aws/sni.png
Binary files differ
diff --git a/static/images/aws/ssl.png b/static/images/aws/ssl.png
new file mode 100644
index 0000000..023367e
--- /dev/null
+++ b/static/images/aws/ssl.png
Binary files differ
diff --git a/static/images/favicon/android-chrome-192x192.png b/static/images/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000..d21933b
--- /dev/null
+++ b/static/images/favicon/android-chrome-192x192.png
Binary files differ
diff --git a/static/images/favicon/android-chrome-512x512.png b/static/images/favicon/android-chrome-512x512.png
new file mode 100644
index 0000000..e027c46
--- /dev/null
+++ b/static/images/favicon/android-chrome-512x512.png
Binary files differ
diff --git a/static/images/favicon/apple-touch-icon.png b/static/images/favicon/apple-touch-icon.png
new file mode 100644
index 0000000..32fa28e
--- /dev/null
+++ b/static/images/favicon/apple-touch-icon.png
Binary files differ
diff --git a/static/images/favicon/favicon-16x16.png b/static/images/favicon/favicon-16x16.png
new file mode 100644
index 0000000..b435c34
--- /dev/null
+++ b/static/images/favicon/favicon-16x16.png
Binary files differ
diff --git a/static/images/favicon/favicon-32x32.png b/static/images/favicon/favicon-32x32.png
new file mode 100644
index 0000000..63eb93c
--- /dev/null
+++ b/static/images/favicon/favicon-32x32.png
Binary files differ
diff --git a/static/images/favicon/favicon.ico b/static/images/favicon/favicon.ico
new file mode 100644
index 0000000..a352255
--- /dev/null
+++ b/static/images/favicon/favicon.ico
Binary files differ
diff --git a/static/images/favicon/site.webmanifest b/static/images/favicon/site.webmanifest
new file mode 100644
index 0000000..45dc8a2
--- /dev/null
+++ b/static/images/favicon/site.webmanifest
@@ -0,0 +1 @@
+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..0e2aaa5
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,153 @@
+html {
+ --width: 800px;
+ --font-main: "DM Sans", sans-serif;
+ --font-secondary: "DM Sans", sans-serif;
+ --font-scale: 1em;
+ --background-color: #fff;
+ --heading-color: #222;
+ --text-color: #444d;
+ --link-color: #3273dc;
+ --visited-color: #8b6fcb;
+ --code-background-color: #333;
+ --code-color: #ddd;
+ --blockquote-color: #222;
+ --border-color: #ccc;
+}
+
+body {
+ font-family: var(--font-secondary);
+ font-size: var(--font-scale);
+ margin: auto;
+ padding: 20px;
+ max-width: var(--width);
+ text-align: left;
+ background-color: var(--background-color);
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ line-height: 1.5;
+ color: var(--text-color);
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: var(--font-main);
+ color: var(--heading-color);
+}
+
+a {
+ color: var(--link-color);
+ cursor: pointer;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+header {
+ display: flex;
+ border-bottom: 1px dashed var(--border-color);
+ padding-bottom: 10px;
+ justify-content: space-between;
+}
+
+header button {
+ align-self: flex-end;
+}
+
+header img {
+ height: auto;
+ max-width: 5rem;
+}
+
+nav a {
+ margin-right: 8px;
+}
+
+strong,
+b {
+ color: var(--heading-color);
+}
+
+button {
+ margin: 0;
+ cursor: pointer;
+}
+
+main {
+ line-height: 1.6;
+}
+
+table {
+ width: 100%;
+}
+
+hr {
+ border: 0;
+ border-top: 1px dashed;
+}
+
+img {
+ max-width: 100%;
+}
+
+code {
+ font-family: monospace;
+ font-size: 14px !important;
+ padding: 2px;
+ background-color: var(--code-background-color);
+ color: var(--code-color);
+ border-radius: 3px;
+}
+
+blockquote {
+ border-left: 1px solid #999;
+ color: var(--blockquote-color);
+ padding-left: 20px;
+ font-style: italic;
+}
+
+footer {
+ padding: 25px 0;
+ text-align: center;
+}
+
+.title:hover {
+ text-decoration: none;
+}
+
+.title h1 {
+ font-size: 1.5em;
+}
+
+.inline {
+ width: auto !important;
+}
+
+pre {
+ border-radius: 5px;
+ overflow: scroll;
+ padding: 0px 15px;
+}
+
+/* blog post list */
+ul.blog-posts {
+ list-style-type: none;
+ padding: unset;
+}
+
+ul.blog-posts li {
+ display: flex;
+}
+
+ul.blog-posts li span {
+ flex: 0 0 130px;
+}
+
+ul.blog-posts li a:visited {
+ color: var(--visited-color);
+}