From db6b05a31aecb5a91044771d4ea0e4e31b82a7ba Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 22:09:42 +0100 Subject: [PATCH 01/94] Initial commit Add vault files Quartz sync: Sep 13, 2023, 8:06 PM Quartz sync: Sep 13, 2023, 8:50 PM Remove leftover Quartz sync: Sep 13, 2023, 8:59 PM Quartz sync: Sep 13, 2023, 9:00 PM Quartz sync: Sep 13, 2023, 9:02 PM Quartz sync: Sep 13, 2023, 9:04 PM Quartz sync: Sep 13, 2023, 9:11 PM Quartz sync: Sep 13, 2023, 9:24 PM Quartz sync: Sep 13, 2023, 9:27 PM Quartz sync: Sep 13, 2023, 9:31 PM Quartz sync: Sep 13, 2023, 9:36 PM Quartz sync: Sep 13, 2023, 9:39 PM Quartz sync: Sep 13, 2023, 9:43 PM Quartz sync: Sep 13, 2023, 9:59 PM Quartz sync: Sep 13, 2023, 10:06 PM --- content/index.md | 45 ++++ content/infra/Alertmanager.md | 14 ++ content/infra/Kube State Metrics.md | 23 ++ content/infra/Kubernetes.md | 40 ++++ content/infra/Linux Filesystems.md | 22 ++ content/infra/Networking.md | 18 ++ content/infra/Postgres.md | 63 ++++++ content/infra/SQLite.md | 13 ++ content/infra/Systemd.md | 40 ++++ content/life/2022.md | 93 ++++++++ content/links.md | 9 + content/motorcycles/701.md | 14 ++ .../posts/Geospatial queries with MongoDB.md | 60 ++++++ content/posts/I just want to get shit done.md | 22 ++ ... learned with Swift and iOS development.md | 198 ++++++++++++++++++ content/posts/My backup script.md | 55 +++++ content/posts/Personal wiki.md | 31 +++ content/posts/Rest APIs and SwiftUI.md | 57 +++++ content/posts/dont forget the human.md | 43 ++++ content/posts/index.md | 5 + content/posts/public notes.md | 21 ++ .../exporting prometheus metrics with axum.md | 102 +++++++++ .../geospatial queries with mongodb.md | 63 ++++++ content/programming/index.md | 6 + content/programming/python.md | 33 +++ content/programming/rest apis and swiftui.md | 57 +++++ content/programming/rust.md | 42 ++++ content/projects/Atuin.md | 16 ++ content/projects/Keyboard.md | 43 ++++ content/projects/iPod.md | 102 +++++++++ content/projects/index.md | 5 + .../projects/yeet a personal image host.md | 6 + content/publish.js | 5 + content/tools/Git.md | 26 +++ content/tools/jq.md | 20 ++ content/tools/mdbook.md | 49 +++++ quartz.config.ts | 14 +- quartz.layout.ts | 11 +- quartz/components/Head.tsx | 8 + quartz/styles/custom.scss | 23 ++ 40 files changed, 1507 insertions(+), 10 deletions(-) create mode 100644 content/index.md create mode 100644 content/infra/Alertmanager.md create mode 100644 content/infra/Kube State Metrics.md create mode 100644 content/infra/Kubernetes.md create mode 100644 content/infra/Linux Filesystems.md create mode 100644 content/infra/Networking.md create mode 100644 content/infra/Postgres.md create mode 100644 content/infra/SQLite.md create mode 100644 content/infra/Systemd.md create mode 100644 content/life/2022.md create mode 100644 content/links.md create mode 100644 content/motorcycles/701.md create mode 100644 content/posts/Geospatial queries with MongoDB.md create mode 100644 content/posts/I just want to get shit done.md create mode 100644 content/posts/Lessons learned with Swift and iOS development.md create mode 100644 content/posts/My backup script.md create mode 100644 content/posts/Personal wiki.md create mode 100644 content/posts/Rest APIs and SwiftUI.md create mode 100644 content/posts/dont forget the human.md create mode 100644 content/posts/index.md create mode 100644 content/posts/public notes.md create mode 100644 content/programming/exporting prometheus metrics with axum.md create mode 100644 content/programming/geospatial queries with mongodb.md create mode 100644 content/programming/index.md create mode 100644 content/programming/python.md create mode 100644 content/programming/rest apis and swiftui.md create mode 100644 content/programming/rust.md create mode 100644 content/projects/Atuin.md create mode 100644 content/projects/Keyboard.md create mode 100644 content/projects/iPod.md create mode 100644 content/projects/index.md create mode 100644 content/projects/yeet a personal image host.md create mode 100644 content/publish.js create mode 100644 content/tools/Git.md create mode 100644 content/tools/jq.md create mode 100644 content/tools/mdbook.md diff --git a/content/index.md b/content/index.md new file mode 100644 index 00000000..e27c00dc --- /dev/null +++ b/content/index.md @@ -0,0 +1,45 @@ +--- +title: Ellie's Notes +date: 2023-09-01 +--- + + +
+
+ +# 👋 Welcome! + +My full name is Ellie Huxtable. I'm a software/infrastructure engineer, and am at my happiest when I'm building something cool. I love an adventure, and if I'm not at a computer there's a good chance I'm riding a motorcycle. + +Here I am trying to maintain a personal wiki, or a second brain. I often explore many technologies or ideas, and then promptly completely forget them. I've found that writing my learnings and thoughts down is immensely helpful. + +
+ +
+ +
+ +
+ +This site is constantly shifting, but here are some things you may be interested in +- [Posts](/posts), a selection of my longer-form writing and thoughts +- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod]] and [[atuin]]. +- [Life](/life), where I'm writing about my travels, adventures, and life in general +- I also keep some notes on [programming](/programming), [infra](/infra), and [tools](/tools). Have an explore! + +## Speaking +I gave my first talk at [FOSDEM in 2023](https://www.youtube.com/watch?v=uyRmV19qJ2o), and am looking forward to giving more in the future! + +## Work + +I currently lead the infrastructure team at [PostHog](https://posthog.com/?ref=ellie.wtf), regularly working with Kubernetes and Terraform. + +Previously, I've worked at [Coinbase](https://coinbase.com), [Tracr](https://tracr.com) and [Arachnys](https://arachnys.com). You can stalk my [Linkedin](https://linkedin.com/in/elliehuxtable) if you really want to. + +## Contact +Please do get in touch! + +Email: ellie @ \
+GitHub: [@ellie](https://github.com/ellie)
+Mastodon: [@ellie@hachyderm.io](https://hachyderm.io/@ellie)
+Twitter: [@ellie_huxtable](https://twitter.com/ellie_huxtable)
\ No newline at end of file diff --git a/content/infra/Alertmanager.md b/content/infra/Alertmanager.md new file mode 100644 index 00000000..99a6b6a9 --- /dev/null +++ b/content/infra/Alertmanager.md @@ -0,0 +1,14 @@ +--- +permalink: infra/alertmanager +date: 2023-09-01 +--- +# Alertmanager + +## Trigger alert manually +Useful to test that you've actually set it all up properly! + +``` +curl -H 'Content-Type: application/json' -d '[{"labels":{"alertname":"test-alert"}}]' http://alertmanager:9093/api/v1/alerts + +-> {"status": "success"} +``` \ No newline at end of file diff --git a/content/infra/Kube State Metrics.md b/content/infra/Kube State Metrics.md new file mode 100644 index 00000000..015b0f2c --- /dev/null +++ b/content/infra/Kube State Metrics.md @@ -0,0 +1,23 @@ +--- +permalink: infra/kube-state-metrics +date: 2023-09-01 +--- +# Kube State Metrics + +Kube state metrics exposes kubernetes metrics in a way that they can be scraped by prometheus. It uses the kubernetes API to create a snapshot of the state of your cluster, and then exposes that on `/metrics`, ready to be ingested by your monitoring. + +### Scaling + +With larger clusters, you might find that scrapes begin to take time - ksm might be exposing millions of samples, depending on how many nodes/pods/etc you have! + +KSM supports sharding, both [automatically](https://github.com/kubernetes/kube-state-metrics#automated-sharding) and manually. Unfortunately the autosharding is still very much in development + +> Automatic sharding allows each shard to discover its nominal position when deployed in a StatefulSet which is useful for automatically configuring sharding. **This is an experimental feature and may be broken or removed without notice.** + +So, we could manually shard! This still isn't amazing though, as even if we have a bunch of pods scraping the same resource, they still each need to pull all the data. So we would go from having one pod pulling x data, to having n pods pulling x data. Or, `n * x` total network transfer, rather than `n * x/n` data transferred if we were only transferring what's required for that shard. Still not ideal. + +> Each shard decides whether the object is handled by the respective instance of kube-state-metrics or not. Note that this means all instances of kube-state-metrics, even if sharded, will have the network traffic and the resource consumption for unmarshaling objects for all objects, not just the ones they are responsible for. + +The conclusion I've come to thus far, is to shard by data type. Pods tend to expose the most samples, so it makes sense to break those out first. So we have one deployment exposing pod metrics, and then one for everything else. + +It seems like this is what [has been done elsewhere](https://www.datadoghq.com/blog/engineering/our-journey-taking-kubernetes-state-metrics-to-the-next-level/), and works for now \ No newline at end of file diff --git a/content/infra/Kubernetes.md b/content/infra/Kubernetes.md new file mode 100644 index 00000000..1121811b --- /dev/null +++ b/content/infra/Kubernetes.md @@ -0,0 +1,40 @@ +--- +permalink: infra/kubernetes +date: 2023-09-01 +--- +# Kubernetes + +## Operations + +### Resizing a PVC for a StatefulSet + +If you've ever tried to resize a statefulset PVC for a database deployment (or similar), you have probably seen this error + +> Internal server error +> +> StatefulSet.apps "es-cluster" is invalid: spec: Forbidden: updates to statefulset > spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden. + +1. Edit all the PVCs manually: `kubectl edit pvc -n ` +2. Delete the StatefulSet, but leave the pods running: `kubectl delete -n statefulset --cascade=orphan ` +3. Recreate the StatefulSet with `helm upgrade`, `kubectl apply`, or however else you deploy stuff +4. Restart the StatefulSet to redeploy all the pods `kubectl rollout restart statefulset -n namespace ` + +## Helmfile + +Helm is totally adequate (I'm uncomfortable calling it good) for deploying an +app, and potentially a small number of dependencies. It can quickly spiral out +of control when there are many services that need deploying, with varying +levels of dependency. + +Helmfile is great for describing a large set of charts to deploy, and their +values. It's nice to be able to set values per environment, and to be able to +deploy charts from a variety of sources - git repos, official repos, and the +local filesystem. + +### write-values + +Ensuring you get the correct YAML and Go template encantation can be annoying, +and testing it out by deploying is not ideal. In lieu of proper testing, +`helmfile write-values` is great as it runs all the templating and dumps files +on disk for inspection + diff --git a/content/infra/Linux Filesystems.md b/content/infra/Linux Filesystems.md new file mode 100644 index 00000000..7d5b92ff --- /dev/null +++ b/content/infra/Linux Filesystems.md @@ -0,0 +1,22 @@ +--- +permalink: infra/filesystems +date: 2023-09-01 +--- + +Just some Linux-y notes + +### Filesystem types +``` +fd00 - Linux RAID +``` + +### Creating software RAID device +``` +mdadm --create --verbose /dev/md --level= --raid-devices= /dev/one /dev/two ... +``` + +### Get device UUID + +``` +blkid +``` \ No newline at end of file diff --git a/content/infra/Networking.md b/content/infra/Networking.md new file mode 100644 index 00000000..c4c48289 --- /dev/null +++ b/content/infra/Networking.md @@ -0,0 +1,18 @@ +--- +permalink: infra/networking +date: 2023-09-01 +--- +# Networking + +### Interfaces +- Interface names must be <16 chars + +### Create bridge +``` +ip link add br0 type bridge +``` + +### Add IP to bridge +``` +ip addr add 172.16.0.1/16 dev br0 +``` diff --git a/content/infra/Postgres.md b/content/infra/Postgres.md new file mode 100644 index 00000000..4682fd4b --- /dev/null +++ b/content/infra/Postgres.md @@ -0,0 +1,63 @@ +--- +permalink: infra/postgres +date: 2023-09-01 +--- +# PostgreSQL + +- `pg_ctl init -D path` - init new database + config at `path` +- `pg_hba.conf` - configure host based auth +- `pg_ident.conf` - map system users to database users +- `postgresql.conf` - all the other config changes + +## Learnings +- It's faster to create a table with no index, copy data in, then add indices + +## Snippets + +### Commands + +- `\l` list databases +- `\c dbname` connect to database as current user + +### Create table as copy of another +``` +create table new_table as table old_table; +``` +Note: this will copy all data, but no indices or constraints + +For no data + +``` +create table new_table as table old_table with no data; +``` + +If you'd like to query/filter it: + +``` +create table new_table as (select * from old_table where some_condition); +``` + +### Check for waiting locks +``` +select relation::regclass, * from pg_locks where not granted; +``` + +### Get database size + +``` +SELECT pg_size_pretty(pg_database_size('database name')); +``` + +### Monitoring replication slots +``` +SELECT * FROM pg_replication_slots; +``` + +### Monitoring replication lag +``` +SELECT extract(epoch from now() - pg_last_xact_replay_timestamp()) AS replica_lag +``` + + +## Useful tools +- [WAL streaming and backup via object storage](https://github.com/wal-g/wal-g) diff --git a/content/infra/SQLite.md b/content/infra/SQLite.md new file mode 100644 index 00000000..1fb3f364 --- /dev/null +++ b/content/infra/SQLite.md @@ -0,0 +1,13 @@ +--- +permalink: infra/sqlite +date: 2023-09-01 +--- +# SQLite + +SQLite is pretty up there as one of my favourite pieces of software! It's reliable, performant, and has never let me down. + +I use it for [atuin](atuin.md). + +## Thoughts +- Enable the WAL. It's fantastic, boosting performance and durability +- The FTS modules are actually surprisingly effective, and very fast diff --git a/content/infra/Systemd.md b/content/infra/Systemd.md new file mode 100644 index 00000000..a3f0834e --- /dev/null +++ b/content/infra/Systemd.md @@ -0,0 +1,40 @@ +--- +permalink: infra/systemd +date: 2023-09-01 +--- + +# Systemd + +Just some issues I have had with it + +## /dev/shm clearing + +This can also be seen by logs such as `could not open shared memory segment "/PostgreSQL.271741757"`. + +For non-system (UID < 1000) users, `logind` will by default clear shared mem files upon logout. For things like databases, this is not at all desireable. + +Setting `RemoveIPC=no` in `/etc/systemd/logind.conf` should resolve this. + +Man page + +``` +RemoveIPC= + Controls whether System V and POSIX IPC objects belonging to + the user shall be removed when the user fully logs out. Takes + a boolean argument. If enabled, the user may not consume IPC + resources after the last of the user's sessions terminated. + This covers System V semaphores, shared memory and message + queues, as well as POSIX shared memory and message queues. + Note that IPC objects of the root user and other system users + are excluded from the effect of this setting. Defaults to + "yes". +``` + +Once changed, a full reboot will ensure the config is loaded. Alternatively, a restart of `logind` should also resolve this: `systemctl restart systemd-logind.service` + +Useful links: + +- https://github.com/systemd/systemd/issues/4532 +- https://github.com/systemd/systemd/issues/2039 +- https://stackoverflow.com/questions/49065733/redhat-or-centos7-systemd-may-remove-user-ipc-resources-unexpectedly +- https://stackoverflow.com/questions/73368759/why-systemd-remove-my-shm-file-but-not-postgresqls \ No newline at end of file diff --git a/content/life/2022.md b/content/life/2022.md new file mode 100644 index 00000000..76e42897 --- /dev/null +++ b/content/life/2022.md @@ -0,0 +1,93 @@ +--- +date: 2023-01-10 +cover: https://img.ellie.wtf/i/2e8befc770a7ab4cf77280710d3851a79217c5512bd4adb1faf4ef279306d26c.jpg +description: 2022 has probably been one of the best years of my life, so I'd like to do my best to document everything that has happened - away from social media, and on something a little longer form! +title: 2022, my year in review +--- +![](https://img.ellie.wtf/i/2e8befc770a7ab4cf77280710d3851a79217c5512bd4adb1faf4ef279306d26c.jpg) + +2022 has probably been one of the best years of my life, so I'd like to do my best to document everything that has happened - away from social media, and on something a little longer form! + +I try to take the possibility that every day could be my last pretty seriously, and I'm really happy that I can look back on this year and feel like I've made the most of it. +I'm starting writing this several months before the year ends, but I'd like to publish it in January 2023. Hope all is going well, future me! + +This is another photo-heavy post from me, as I'm telling a story and that's best done with imagery + +# Tech +## iPod +After visiting the design museum in London and seeing some old iPods, I was inspired to have a go at [modding one](ipod.md). I did this in a couple of hours after spending the day at a motorcycle show, posted some photos online, and it really took off. First a lot of attention on Twitter; then a bunch in the press - including a small interview and feature with [Vice](https://www.vice.com/en/article/qjbexd/a-software-engineer-upgraded-an-old-ipod-for-2022?ref=ellie.wtf). + +## Atuin +This year I continued to work on [Atuin](/projects/atuin) - albeit fairly slowly. We had a couple of magazine features (which I still can't believe), and gained even more contributors! Thank you to everyone that has helped make Atuin better <3 + +I also opened up a GitHub sponsors. This was for two reasons really. Firstly, I was paying for a server to host the public sync instance, and wanted to reduce the financial burden. Secondly, I want to try and ensure Atuin has a sustainable future. +So far this is paying for the server bills, with a little left over! I never thought this would happen, thanks everyone 💖 + +# Work +I started the year working on the infra team at Coinbase. As a subset of the cloud team, I was mostly managing the dedicated resources we used for macOS CI. +However, in August, due to a bunch of factors + after 2.5yrs, I left Coinbase for something fresh. I'm currently leading the infra team at PostHog and solving some different challenges! + +We're also hiring, so if you'd like to work on some cool problems with me (you poor soul), then please check us out! + +# Adventure +I've always wanted to travel more (who hasn't?). Prior to COVID, a combination of not enough money + working way too many hours meant that I barely left London, let alone the UK. Seeing as there were fewer travel restrictions + both my finances and work/life balance were doing better, I tried to fit in some more adventure. + +The first trip of the year was to the Pyrenees, where I tried snowboarding for the first time. I wasn't really a fan of the sport, but I've always loved mountains. So instead I spent some time messing about with snowmobiles and hiking + +![](https://img.ellie.wtf/i/9e0a592cd53ec77cf0e5daaeff15a458d84626f18577a4719836ec7df38ff497.jpg) + +I was actually in the middle of a valley, alone, with super spotty phone reception, when the news of Russia invading Ukraine broke. I didn't have good enough data to find out if the world was ending, so tried to make the most of the alpine calm. + +![](https://img.ellie.wtf/i/dd97eec83f4c708cab92bdb661fa030250c26c62c9fa62eacea6c099632229d7.jpg) + +At the end of the trip, I spent a day in Barcelona by myself, while my friends finished up the snowboarding. I wanted to work on my self confidence last year, so I had challenged myself to spent more time exploring alone. I think this went pretty well, and is definitely an exercise I'll be carrying into 2023! + +In March/April, I went for a trip to California. I've always fancied visiting, so booked it in a bit of a YOLO moment. I spent just over a week in San Francisco, renting a motorbike (CB1000R) and working from the Coinbase office for a bit. I really enjoyed the Bay Area, and certainly made the most of how accessible the countryside is from the city! + +For the second week, I met some friends from London in LA, where we attended the Bike Shed LA opening party + rode some canyons (I rented a Panigale V4, as it was also my 23rd birthday...). + +![](https://img.ellie.wtf/i/8b3ee7b86664ff98302e2126ca995fb3afe58991895e25d266845d8520a25936.jpg) + +The bay was great, but to be totally honest LA wasn't really for me. Driving everywhere was super annoying, as I like walking. It also felt far too fake and ingenuine for me. + +During the SF part of the trip, I made some absolutely fantastic friends - I actually went back later in the year to visit them! A special thank you to Michelle, who let me stay in her spare room and borrow her motorcycle the second time around. I stayed for 6 weeks that time (with a little trip to Hawaii), and it was only possible because of her. We also had a fantastic weekend riding trip to Yosemite, which is probably up there as my favourite few days this year. + +I'll be back soon! + +![](https://img.ellie.wtf/i/08391a7ffaad6f9b7a28aad91c291403c804e4515921742df07ceccb3ddc5f1e.jpg) + +Over the summer, I toured Europe with my friend Laura, doing 2000 miles in a week on a bike I'd bought just before leaving (Husqvarna 701 Supermoto). We rode through France, Germany, Switzerland, Italy, and also visited Monaco! My first ever time at a casino was the Monte Carlo, where I managed to make 10 EUR 💪 + +![](https://img.ellie.wtf/i/a4d8775ac7184ebea6d9e96064a1c32a6f227b4645845331443d90a535de77e0.jpg) + +# Motorcycles +If it's not already obvious from the above, motorcycles are a pretty big part of my life. I attended a whole bunch of track days, learned to get my knee down (and got much faster in the process), and went on a tour. + +![](https://img.ellie.wtf/i/a55a3101e3f09ab084e911c7cfc121eefef3fbefeabcaef5b80c295efe42524a.jpg) + +Along the way, I made some new friends too! Motorcycles have had a huge impact one me, and are now a pretty strong part of my social life. + +In 2023 I'm hoping to get some more track time, try some riding disciplines I haven't had much time for (hello off-roading), as well as more moto-travelling. I can't wait to meet more people along the way 😊 + +# Photography +I've always fancied trying photography - and in 2022 I did! I bought an A6400 and a couple of Sigma prime lenses from a friend, with their first proper outing being the Europe tour. I've really enjoyed taking photos this year, and I'm really looking forward to learning more in 2023. + +![](https://img.ellie.wtf/i/7e57bb3234807a898b660338e6691f3b595738f67dd407091874c7e1aa01e28f.jpg) +# Gym +2022 is also the first year where I pretty consistently went to the gym! I've been on-and-off forever, but starting in November 2021 I starting regularly lifting weights. The impact on my health and mood has been immense, and I've now found a new hobby. +I'm hoping to really up the intensity in 2023, and also work some more on getting my diet right. + +# London +Towards the end of the year, I realised what was important to me with where I lived. If anything, this was highlighted by visiting SF. + +I spent most of my summer outdoors - and the best outdoors (imo) is not in London. So I could also say that I spent a good portion of my summer driving/riding in and out of the city. This lead me to the conclusion that living in London doesn't really fit my life any more. + +I also work from home, so paying London rent, London cost of living, and having to travel to get good green spaces just doesn't make sense. + +In early 2023, I'll be moving to St Albans. Still close enough to go into the city, but with the countryside being much more accessible. And the rest of the world too! I've living in London all of my adult life, despite growing up in a very rural area. + +Hopefully I'll be writing my 2023 review and saying that this was a good decision :) + +# Conclusion +2022 was great, and hopefully 2023 is going to be better. I wrote this as a total afterthought + missed a LOT, so next year I'll be trying to keep little notes as I go. +I'd like to learn more, explore more, write more, and meet more people. Let's see how it goes! \ No newline at end of file diff --git a/content/links.md b/content/links.md new file mode 100644 index 00000000..1fbea1c3 --- /dev/null +++ b/content/links.md @@ -0,0 +1,9 @@ +--- +permalink: links +date: 2023-09-01 +--- +- [time2reach](https://map.henryn.xyz/) Shows the time to travel from a given point. Useful for house hunting, known as an [Isochrone map](https://en.wikipedia.org/wiki/Isochrone_map) +- [pgcat](https://github.com/postgresml/pgcat), a postgres proxy #database #rust +- [supavisor](https://supabase.com/blog/supavisor-1-million) , more postgres proxy-ing #database #elixir +- [pg_partman](https://github.com/pgpartman/pg_partman/), postgres partition management extension #database +- [pueue](https://github.com/Nukesor/pueue), a shell process manager #shell \ No newline at end of file diff --git a/content/motorcycles/701.md b/content/motorcycles/701.md new file mode 100644 index 00000000..0e849d9a --- /dev/null +++ b/content/motorcycles/701.md @@ -0,0 +1,14 @@ +--- +date: 2023-09-01 +title: Husqvarna 701 Supermoto +--- + +I've had my 701 supermoto since June 2022, and absolutely love it. In [[2022]] I rode it for 2000 miles around Europe! + +# Remap +The bike runs super lean and hot from the factory. Decat + new headers + can will result in the bike being dangerously lean, and a remap is recommended. Besides, you won't be making the most of the new parts without it! + +- Power commander 6 required if you go the PCM route. It requires a fueling plug _in addition_ +- Some companies can remap the ECU directly +- [Coober ECU](https://www.coober.eu/product/hqv-701-series-lm-ecu/) is an option +- Older models can take a fueling plug, but I don't believe this is an option in the 2022 \ No newline at end of file diff --git a/content/posts/Geospatial queries with MongoDB.md b/content/posts/Geospatial queries with MongoDB.md new file mode 100644 index 00000000..958cc744 --- /dev/null +++ b/content/posts/Geospatial queries with MongoDB.md @@ -0,0 +1,60 @@ +--- +title: Geospatial queries with MongoDB +date: 2019-07-25 +permalink: posts/geospatial-queries-with-mongodb +--- +I'm currently playing with MongoDB and its [geospatial queries](https://docs.mongodb.com/manual/geospatial-queries/?ref=ellie.wtf). It’s pretty interesting so far, so I just thought I'd write something up to show how I'm using it with PyMongo! + +Firstly, the obvious `pip install pymongo` is needed. We will need a mongo client first, which is easy enough + +```python +from pymongo import MongoClient + +client = MongoClient() +``` + +It’s pretty neat - by default, MongoClient will connect to `mongodb://localhost:27017`. + +You might want to do something like this instead though + +```python +url = os.environ.get("MONGO_URL", "mongodb://localhost:27017") +client = MongoClient(url) +``` + +We're going to want to create a MongoDB index on a document field to allow the geospatial magic to work. We will be creating a [`2dsphere`](https://docs.mongodb.com/manual/core/2dsphere/?ref=ellie.wtf) index. + +```python +from pymongo import MongoClient, GEOSPHERE, DESCENDING + +client = MongoClient() +db = client.foo +db.bar.create_index([("location", GEOSPHERE)]) +``` + +Now all that is needed is to insert a document and run a query :) We'll insert a document that just has a `location` field + +```python +db.bar.insert_one({ + “location”: { + "coordinates" : [ + 51.4982563, + -0.0861183 + ], + "type" : "Point" + } +}) +``` + +The `location` field needs to be [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON?ref=ellie.wtf), which is a JSON standard for representing geographic features. It makes querying for documents super easy! + +```python +db.bar.find({ + "location": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [LAT, LONG]}, + "$maxDistance": range, + } + } +}) +``` \ No newline at end of file diff --git a/content/posts/I just want to get shit done.md b/content/posts/I just want to get shit done.md new file mode 100644 index 00000000..451d50d3 --- /dev/null +++ b/content/posts/I just want to get shit done.md @@ -0,0 +1,22 @@ +--- +date: 2019-11-19 +permalink: posts/i-just-want-to-get-shit-done +title: I just want to get shit done +--- +Once upon a time, I would tinker with everything. + +My phone would run a custom kernel, a custom ROM, whatever I could do to it. My iPod would be jailbroken. When I first bought an Apple laptop, I put Linux on it - which involved a great deal of tinkering to work smoothly. All of my other computers ran some version of Linux, and I'd always be changing window manager, distro, playing with new and shiny and configuring everything I could. And I learned a lot! + +Now I just run the OS that came on my laptop, with no issues. + +I usually deploy side projects as docker images to k8s/heroku/dokku/whatever means I don't need to SSH into something and manage a "real machine" as often. + +The difference? When I get home from writing code all day at work and have the energy to work on a side project, I don't want to be fixing issues with some flaky DIY setup. I don't want to try to do something and discover that my server is down, or something is broken because I forgot which Raspberry Pi it was running on. I don't want my phone to boot loop because of a weird bug in whatever ROM I am using. I want it to just be a phone, and go back in my pocket as soon as I am done with it. + +I still like tinkering, but I don't like **mandatory** tinkering, tinkering that happens because something I rely on has broken. I'm happier to pay a subscription for someone else to manage my things for me. + +I think the difference is a change in mindset. I've gone from seeing my tools themselves as small projects that help me build bigger projects, to just being tools that let me get shit done. If a tool breaks, I can replace it/return it/whatever. + +That all being said, I think everyone should spend some time tinkering. I learned an awful lot from a few years of customising my setup, and well... stuff breaking. I just don't want to have to fix my own things any more, if I can help it. + +> though this does totally miss that I'm currently trying to get a dell blade setup in my house, but that's not the point 🙈there's still some learning I want to get done there! \ No newline at end of file diff --git a/content/posts/Lessons learned with Swift and iOS development.md b/content/posts/Lessons learned with Swift and iOS development.md new file mode 100644 index 00000000..0e0820d1 --- /dev/null +++ b/content/posts/Lessons learned with Swift and iOS development.md @@ -0,0 +1,198 @@ +--- +date: 2020-01-29 +permalink: posts/lessons-learned-with-swift-ios-development +tags: + - swift +title: Lessons learned with Swift and iOS development +--- +I'd like to start this off by saying that I am _in no means an expert_. While I have been programming for a long time, I'm new to the world of Swift and iOS, and have never built a mobile app :) This is everything I've stumbled upon and learned while building the first iOS version of [Pillion](https://pillion.bike/?ref=ellie.wtf)! + +> Pillion is an app to aid in finding motorcycle parking - I've got maybe 10,000 parking locations listed all over the world, some sourced from OpenStreetMaps, some from online datasets, and the rest from usersOther than parking, it also provides ride tracking + +A lot of it was found while I was still figuring things out, so in a bunch of cases I wasn't doing things in the "correct" way. Where I've learned better, I've included both approaches. + +I might write posts going into more detail on each section (MapBox especially) in the future, if this is well received. + +Please let me know if you notice any mistakes 😊 + +## SwiftUI + +I went straight in for using SwiftUI, the new UI system that Apple released with iOS 13. The caveat here is that only devices running iOS >= 13 are supported - though this is everything since the iPhone 6S. + +Seeing as I've been using React for years, SwiftUI feels very nice. While it has differences, components/children/state in ways that feel natural. I actually prefer the state binding to how React handles state, and find that the code feels a decent bit cleaner. + +I never tried UIKit so I don't really have much to compare to, but from what I have seen of SwiftUI, it is much cleaner. + +I have had a few issues with SwiftUI that seem to mostly be related to its immaturity - I cover this below. + +Annoyingly most of the nice UI components I could find on GitHub were only for UIKit. Maybe I'll try to wrap them at some point, but the ecosystem for SwiftUI seems a little sparse - reflecting its immaturity very well. Something to bear in mind. + +## Debugging + +The XCode debugger was a nice surprise! While I'm used to `ipdb` sessions with Python, I didn't expect anything as flexible considering the code was running on my phone + compiled. + +I found the debugger could happily inspect values, evaluate Swift expressions, everything I wanted! Tab completion worked really nicely too. Yay for tooling :) + +It can occasionally be a little slow, but I suppose that's to be expected. + +## UI thread stuff + +A lot of my application is essentially making a request to my API, then rendering the result. I found that attempting to update the UI from a background thread (such as the async handler for a HTTP response) would result in an error. Instead, this pattern should be used: + +```swift +DispatchQueue.main.async { + // do ui +} +``` + +This makes it super easy to avoid blocking the UI thread 😁 + +This was actually something I read very early into using Swift. I've since learned that `ObservableObjects` with some `Published` properties are more the way to go for fetching and updating stuff, where you can! I've left it here just in case it's needed though. I'm still not that happy with how HTTP requests are handled in my project so it's probably something I am going to revisit in the future. + +## Keyboard hiding + +Just a small thing, but I wanted to be able to hide the keyboard on certain interactions. For instance, when a search bar is focused and you touch an item in the list, hide the keyboard. + +```swift +extension UIApplication { + func hideKeyboard() { + sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} +``` + +```swift +UIApplication.shared.hideKeyboard() +``` + +## Just + +Making HTTP requests in Swift isn't too bad, but I'm used to `requests` in Python - so I looked for something similar. + +[Just](https://github.com/dduan/Just?ref=ellie.wtf) seemed to fit that bill! It's nice, small, fast, and let's you write things like this: + +```swift +Just.get("https://example.com") { r in + print(r.text) +} +``` + +...which is super nice! + +It also lets you setup a session that has headers pre-set, which is useful for authorised HTTP requests + +## Mapbox + +I've been working with the [MapBox Maps SDK for iOS](https://docs.mapbox.com/ios/api/maps/5.4.0/?ref=ellie.wtf) for a large part of this project. Mapbox was a joy to work with in JavaScript, but frankly the iOS side is a little lacking. It can be awkward to customise things,  and the documentation isn't the best. I often found multiple ways to do something, with little explanation as to why. Seems like Android might be a bit better here though! + +I'm not saying it's _bad_, just that it feels a tad unfinished or underdeveloped in comparison to the JS solution - and from what I've seen of the Android version, compared to that too. I'll do a comparison once I've built the Android app. + +Something else here is that some of the docs are more for UIKit - makes sense as SwiftUI is so new! Just something to bear in mind. There are some docs that show you how to wrap your map for SwiftUI though + +## Swift Typechecking + +Coming from Rust, the Swift typechecker feels a little... lacking. Occasionally it flat out fails to infer a seemingly obvious type, or takes too long and asks the developer to break the code down a bit. Not really a huge problem, but it is a bit annoying. Apparently this is constantly improving though! + +In some cases it will highlight an error in one location, when there are no issues there. Deleting the code will show the actual underlying issue. I think this is more to do with the SwiftUI DSL. When there is a type error within the DSL, if it can't infer the type then it just kinda falls apart. I can't find a good example right now, but will update this when I do. + +After updating to Catalina and the latest XCode, I had a bunch of weird "ambiguous member" errors that never existed before the upgrade. Swift seems to have a tendency to give misleading error messages, which is... strange. I've never used another modern language that does the same thing, especially not one backed by such a large company. + +I found I eventually got better at writing code that Swift could understand, even though what I was writing before was, as far as I know, semantically correct. + +## SwiftFormat + +I'm used to having formatters with other projects, so I looked for one here too. [SwiftFormat](https://github.com/nicklockwood/SwiftFormat?ref=ellie.wtf) seems to work nicely + +## SwiftUI DSL issues + +So with SwiftUI `View` bodies are declared with a little DSL-type-thing + +```swift +var body: some View { + ComponentOne { + AnotherOne { + // so much nesting :O + } + } +} +``` + +...which is all well and good. + +However, when you want to get into conditional rendering, the DSL doesn't seem quite there yet. + +```swift +if thing.ouch { + RenderMe(thing.ouch.thing) +} +``` + +However, things such as `if let`, at time of writing, do not work + +``` +if let o = thing.ouch { + RenderMe(o.thing) +} +``` + +Not essential, but would be nice! + +There are a few other small issues here and there with the DSL that still need sorting, but SwiftUI is very new/beta so I'm not surprised there. Other than the issues, it is a joy to work with. When it works, it works really nicely! It's just when it doesn't ;) + +## Auth0 + +I'm currently using Auth0 lock, with Google + Apple oauth login. I'm actually using my own solution now instead, but I've left this here as it's still relevant. + +The Apple one is actually really cool! Users can choose to have an Apple forwarding address to hide their email, and login with FaceID. + +Lock wasn't setup for SwiftUI, but it's pretty easy to wrap: + +```swift +struct Auth0Lock: UIViewControllerRepresentable { + typealias UIViewControllerType = LockViewController + var lock: Lock = Lock.passwordless().withOptions { + $0.passwordlessMethod = .magicLink + } + .onAuth { credentials in + print(credentials) + // save access token and use it to do stuff :) + } + .onError{ error in + print(error) + // you should probably do something other than just print errors + } + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> LockViewController { + return lock.controller + } + + func updateUIViewController(_ uiViewController: LockViewController, context: UIViewControllerRepresentableContext) { + // there isn't really much updating I need to do atm + } +} + +``` + +Auth0 comes with a credential manager that handles refresh tokens, requesting new access tokens, saving and validating tokens, etc. So you don't really need to put a lot of work in to keep your users logged in! + +> just to elaborate slightly on why I dropped Auth0I wanted more flexibility with how auth was handled, and I ended up rewriting my backend as a Django app - so it was pretty easy for me to just integrate with what Django already has.I switched to Django so I could build the product faster, and not spend time thinking about fun architecture things + +## Apple developer + +Signing up for an Apple Developer account for my company (Pillion Software Ltd) took maybe a week or two - filling in the online forms was fast, but it took a little while to get approved + they had to call me. All good after that though! + +I tried to use Fastlane to take screenshots, but found that the simulators/tests didn't work very well (mapbox crashing in UI tests) so the only workable way was for me to just take a few manually. Not great, and I'd like to sort that. I'll get around to it eventually. It also doesn't help that my laptop is super slow + a bit old (mostly lacking in RAM), so the simulator is really crappy. + +I was told after my first app store submission that my app didn't have enough functionality. Ouch. After some research, it seems like anything that _could_ be a web app, Apple aren't that keen on approving. So instead I just brought some things forward in my roadmap. + +I actually had a version of my app go through the entire review process (from me pressing "submit" to it being live) in about 7 minutes, which I was very impressed with. + +## Conclusions + +In hindsight I think I'd probably research other solutions before diving in to using the same tech as I did for my old web version. Maybe MapKit would have done the job just fine and I wouldn't have used MapBox. + +I did learn a lot in this process though, so I don't think I'd change too much. I'll be starting on building the same thing for Android soon (and I don't really know any Android dev yet either ;P) so we will see how that goes! Expect a post similar to this one. + +Anyway, [Pillion is now in the App Store!](https://pillion.bike/ios?ref=ellie.wtf) + +If you bike, and decide to give it a go, I'd love to know what you think - so please don't hesitate to get in touch! \ No newline at end of file diff --git a/content/posts/My backup script.md b/content/posts/My backup script.md new file mode 100644 index 00000000..cdb0c7cd --- /dev/null +++ b/content/posts/My backup script.md @@ -0,0 +1,55 @@ +--- +date: 2020-01-08 +permalink: posts/my-backup-script +title: My backup script +--- +I recently bought a 1TiB Samsung T5 SSD - pretty damn surprised I can get something of such capacity in something about the same size as a credit card! The last SSD I bought cost about the same but was 120GiB 😂 + +Anyway, I wanted to make sure I was properly backup up my laptop. I plan on using it for a lot more than just laptop backups though, and I didn't want to partition the drive to use it with Time Machine + +Soooo instead, I'm creating `.tar.gz` archives :) Simple and easy. + +I'll be updating this page as I update the script, but I'm basically `tar`ing up my entire home directory with some exclusions. Note that these exclusions are _Mac OS specific_, so if you're using Linux you're probably going to want to have different settings + +```bash +BACKUP=backup-macbook-$(date +%FT%H:%M:%S).tar.gz + +tar -cvpzf $BACKUP \ + --exclude=$BACKUP \ + --exclude=.cache \ + --exclude=.debug \ + --exclude=.local/lib \ + --exclude=.local/share/virtualenvs \ + --exclude=.recently-used \ + --exclude=.thumbnails \ + --exclude=.pyenv \ + --exclude=.Trash \ + --exclude=.npm \ + --exclude=.poetry \ + --exclude=.kube \ + --exclude=.fastlane \ + --exclude=.mix \ + --exclude=.pyenv \ + --exclude=.gem \ + --exclude=.vscode \ + --exclude=.cocoapods \ + --exclude=Downloads \ + --exclude=Library \ + --exclude=Movies \ + --exclude=Music \ + --exclude=nltk_data \ + --exclude=Pictures \ + --exclude=pkg \ + --exclude=Applications \ + . +``` + +I'm actually backing up my iCloud photos to this drive as well, using [this script](https://github.com/ndbroadbent/icloud_photos_downloader?ref=ellie.wtf). It's really good! Doesn't seem to be maintained any more, but does the job perfectly. + +It's probably a good idea to encrypt anything sensitive, which backups and photos probably are. + +GPG is pretty useful for this: + +```bash +gpg --symmetric --cipher-algo AES256 $BACKUP +``` \ No newline at end of file diff --git a/content/posts/Personal wiki.md b/content/posts/Personal wiki.md new file mode 100644 index 00000000..9e4d82ea --- /dev/null +++ b/content/posts/Personal wiki.md @@ -0,0 +1,31 @@ +--- +permalink: posts/personal-wiki +title: I've started a personal wiki +date: 2020-04-10 +description: There's not much there at the moment, but it's something I've been meaning to do for a while. +--- +> So it turns out this didn't work for me.I'll probably be writing something later on, when I figure out why! + +You can find it [here!](https://ellie.wiki/?ref=ellie.wtf) + +I have lots of projects, some are work, some are for fun, and in all of them I spend a lot of time learning new things + +Frustratingly, I can't remember them all :( + +I've read a lot about the value of keeping an engineering notebook/journal, and it's all something I agree with! I've attempted this in the past, but never really got anywhere with it. + +I've tried keeping a `notes.txt` file, but eventually I find organisation becomes a bit of a hassle. Plus I like nice shiny things with emojis and images, my text editor isn't great for those ✨ + +I've tried paper notebooks - much as they appeal to me, I'm a disaster with them. I lose them, forget them, spend _ages_ finding the perfect pen and paper combo, and then in the end I end up sad because my handwriting is comparable to the death spasms of a spider after falling in an inkwell. + +I love looking at the fancy bullet journals some people have, but I have neither the neatness nor the patience to do something like that for myself. Plus I can hardly paste a code snippet onto paper + +Really, I want 1) markdown and 2) a nice way of rendering my markdown + +So, I've setup a little personal wiki! I'm keeping it separate from my blog because I'd rather have somewhere I feel ok to dump random shitty code snippets without caring too much. Maybe eventually things from there will make their way over here + +It's running on my `lab` (something I'll explain another time), and is basically just a hugo static site. You can find the git repo here - [https://github.com/ellie/wiki](https://github.com/ellie/wiki?ref=ellie.wtf) + +I'll see how it goes, because generally for writing I prefer to have a web UI that I can access from _any_ device, really easily. But I suspect I'll mostly be wanting to write to this when I'm at a computer anyway. Plus, I've been playing with some mobile SSH clients that are... surprisingly nice 🤔 + +Let me know if you have any questions! \ No newline at end of file diff --git a/content/posts/Rest APIs and SwiftUI.md b/content/posts/Rest APIs and SwiftUI.md new file mode 100644 index 00000000..ab64bd06 --- /dev/null +++ b/content/posts/Rest APIs and SwiftUI.md @@ -0,0 +1,57 @@ +--- +date: 2020-02-23 +permalink: posts/rest-apis-and-swiftui +tags: + - swift +title: Rest APIs and SwiftUI +--- + +I recently had someone ask me how I handled fetching and rendering data from a REST API in a SwiftUI view. + +If you're coming from something like React, it's actually handled differently. We can't just fire off a request in `init` or `onAppear`, and update some `@State`. Awkwardness with `escaping` closures, `struct`s not being mutable, etc etc etc. + +Luckily SwiftUI has some constructs that can help :) I won't be going over how to make GET requests or whatever in detail, though I like to use [Just](https://github.com/dduan/Just?ref=ellie.wtf). + +Firsts up, let's have a view + +```swift +struct UserProfile: View { + @ObservedObject user: User = User(username: "elm", name: "Ellie") + + var body: some View { + VStack { + Text("Username: \(self.user.username)") + Text("Name: \(self.user.name)") + } + } +} +``` + +So, how do we get the `user` object here loaded? `@State`? + +Actually no. There's something called an [ObservableObject](https://developer.apple.com/documentation/combine/observableobject?ref=ellie.wtf). This is an object with `@Published` properties. If a `@Published` property is updated, any view containing this object will have an update triggered. + +An example will probably illustrate this more effectively + +``` +struct User: ObservableObject { + @Published var username: String + @Published var name: String + + init(username: String, name: String) { + self.username = username + self.name = name + + self.load() + } + + func load() { + somehowRequestDataAsynchronously(); + // so basically just change `username` or `name` in here + } +} +``` + +The view essentially "observes" something "observable" - `@ObservedObject` and `@ObservableObject`. As soon as a `@Published` property is updated on something we are observing, a re-render is triggered and the view updates! + +Hopefully this clears things up! \ No newline at end of file diff --git a/content/posts/dont forget the human.md b/content/posts/dont forget the human.md new file mode 100644 index 00000000..8ee3e2c4 --- /dev/null +++ b/content/posts/dont forget the human.md @@ -0,0 +1,43 @@ +--- +permalink: posts/dont-forget-the-human +date: 2020-07-27 +title: Don't forget the human +--- + +Computers are cold and heartless. They don't lie, and they don't care if you scream and swear at them. You can abuse them all day long, and they'll still efficiently flip bits and push pixels. + +But... people aren't. Behind every piece of software, there's at least one human. Who has a heart, and who cares what you think. Who has hopes, and dreams, and fears. Who feels. + +This is something I've been wanting to write in one form or another for quite a while now, and I've been rewriting this post for a little too long. + +Sometimes it seems like we forget about the humans. Either we forget that there's a human behind the software we build, or we forget that we're shipping software _for people_. + +It seems to me that more and more, especially recently, nobody can share their work or their ideas without someone stepping in and criticising their ideas. Maybe they could have written it in a language that's faster, uses less memory, _something about bloat or native code._ Maybe it doesn't have a feature that another project does, so why the hell should anyone use it? + +I remember earlier in my career, every time I opened a PR, I'd have a small feeling of anxiety. Was it good enough? What if I'm bad at programming? Would people hate it? What if I'm wrong? Fear of failure is one of the biggest obstacles to success, and all too often it stops people from sharing or achieving what they could. The feeling of "this isn't good enough to share" goes along with that. + +Even while writing this, I couldn't help but feel like it isn't quite perfect, isn't quite good enough to share. But that's the point of my blog; I want to make myself put things out there, even if they're not 100%. + +We should feel like it's ok to share work that isn't finished, that doesn't use the fastest, cleanest, most optimal approach. If I've made something for fun and want to share it, why not? If it doesn't use the technology you prefer, and you haven't paid me to build it, why would you shit all over someone else's work? + +Better yet. If you care that much, make your own. Instead of pulling down something of someone else's', build your own thing up. + +To some degree I understand people not being satisfied with professional software, that they have paid for, and that doesn't meet their expectations. But if it's open source, free, and available for all? Why would you ever complain about that? If it's not for you, just... move on. + +Friends of mine won't share their blog posts to HN, Reddit, etc, because they feel like their words aren't good enough. Or like people there are going to tear them apart. Or because they might have made a small mistake somewhere. + +The main thing I can see fuelling so many arguments is an inherent _need_ to be right. It's been written about so many times before, but for some reason people can't bear the idea that **[someone on the internet is wrong](https://xkcd.com/386/?ref=ellie.wtf)**. + +Except, when it comes to the preferences of which tools or technology someone wants to build something with, in their own time, can you really be wrong? + +There's a stereotype held about people in tech, that we're all that super nerdy person who struggles socially, can't talk to people, and hide all day in the basement programming. And that somehow, this is excusable if you're _really good_ at writing code. But is it? If you're writing code for people to use, and you're working on it with people, not having people skills isn't really an option. Much like technical skills, it isn't an innate quality - you can work on improving your people skills, much like you can your tech skills. + +If you take anything at all from my post, hopefully it's this; if you really must say something, and you really feel like someone made the wrong choice, maybe think about your phrasing. Eg + +> Hey! Nice work. I was curious though, why'd you pick  over ? + +vs + +> Ugh, this would be so much better if it just used . I hate the way modern tech is going + +Let's make our industry a much more friendly and welcoming place 💖 \ No newline at end of file diff --git a/content/posts/index.md b/content/posts/index.md new file mode 100644 index 00000000..6d224b7a --- /dev/null +++ b/content/posts/index.md @@ -0,0 +1,5 @@ +--- +title: Posts +date: 2023-09-01 +--- +A collection of my longer-form thinking and writing, some of it related to programming and some of it not. \ No newline at end of file diff --git a/content/posts/public notes.md b/content/posts/public notes.md new file mode 100644 index 00000000..bf574e61 --- /dev/null +++ b/content/posts/public notes.md @@ -0,0 +1,21 @@ +--- +permalink: posts/public-notes +date: 2023-09-01 +title: Keeping notes in public +--- +> [!info] An update +> I liked this so much that I actually replaced my whole blog with it! Now my blog is just a part of the whole. Quartz updated around the time I wrote this, and it solved a bunch of my frustrations, so I'm using it now. + +I first blogged about having a personal wiki more than [three years ago](https://ellie.wtf/ive-started-a-personal-wiki/) now. It was a nice try at solving the "I'd like to keep notes in public" problem, but it still didn't really do it for me. I kept it up to date for about a month, and then it was abandoned. + +I'd built it with Hugo, a static site generator. The setup was pretty smooth and only required me to edit/update some markdown. + +I've tried static site generators _so many times_, but I've never been truly happy with them. I think because it feels too much like writing code, and I get too distracted trying to automate my setup or tweak settings or whatever. I also don't blog frequently enough to remember all the front matter options, and end up having to check the docs far too much. + +I've seen games developers say to "make games, not engines" and I felt like I was just making an engine. + +Anyways. I've been using [Obsidian](https://obsidian.md/?ref=ellie.wtf) for a while, and decided I'd try it out for a public wiki. To begin with I tried a few setups using static site generators like [Quartz](https://quartz.jzhao.xyz/?ref=ellie.wtf), and the Obsidian [git sync plugin](https://github.com/denolehov/obsidian-git?ref=ellie.wtf). But again, I found myself tinkering with setups and not just taking some notes. Maybe someday I'll go for something more custom, but for now that's not the point. + +[Obsidian Publish](https://obsidian.md/publish?ref=ellie.wtf) solved the problem really nicely for me, and I've been happy with it for the past month or two! I usually update it a few times a week, and I really like the way it presents things. + +I'm still playing with how I organize things a bit (one big page I keep adding to with new things, or lots of little notes?), but what matters most is that I'm actually writing things down. \ No newline at end of file diff --git a/content/programming/exporting prometheus metrics with axum.md b/content/programming/exporting prometheus metrics with axum.md new file mode 100644 index 00000000..7ab3d47f --- /dev/null +++ b/content/programming/exporting prometheus metrics with axum.md @@ -0,0 +1,102 @@ +--- +permalink: programming/exporting-prometheus-metrics-with-axum +draft: false +tags: + - rust + - til +date: 2023-09-13 +title: Exporting Prometheus metrics with Axum +--- +Observability is important! Generally I use Axum as my HTTP framework in [[rust|Rust]], as it's pretty ergonomic to use + fast. + +> [!info] +> [tower-http](https://github.com/tower-rs/tower-http) provides a bunch of useful HTTP middlewares used in a lot of projects. At the moment it does not provide a metrics middleware. Someday it may do! +> +> [Issue](https://github.com/tower-rs/tower-http/issues/57) to track + +There are quite a few crates that do a lot of this automagically for you, but the [Axum example](https://github.com/tokio-rs/axum/blob/368c3ee08fc3896358d3bd2bfc8cc67f2925c6ef/examples/prometheus-metrics/src/main.rs) suggests using [metrics](https://github.com/metrics-rs/metrics). I honestly don't need anything extra complex and just want a `/metrics` endpoint with some counters/etc most of the time - so metrics it is! + +Anyway, first we need to setup the prometheus exporter. This is basically what generates the content of `/metrics`. It uses the `metrics-exporter-prometheus` crate. You'll only want to set up a global recorder in an executable - for a library, you can leave that up to the user. + +I've mostly just lifted this from the Axum example linked above 😇 + +```rust +fn setup_metrics_recorder() -> PrometheusHandle { + const EXPONENTIAL_SECONDS: &[f64] = &[ + 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, + ]; + + PrometheusBuilder::new() + .set_buckets_for_metric( + Matcher::Full("http_requests_duration_seconds".to_string()), + EXPONENTIAL_SECONDS, + ) + .unwrap() + .install_recorder() + .unwrap() +} + +``` + +I'm actually setting up two metrics today, but only `history_requests_duration_seconds` requires some setup. This is because it is a histogram, and we need to tell the exporter how to bucket the data. + +Once that's done, we can write the axum middleware! (lifted from the example, and modified to compile properly. I'll open a PR) + +```rust +/// Middleware to record some common HTTP metrics +/// Generic over B to allow for arbitrary body types (eg Vec, Streams, a deserialized thing, etc) +/// Someday tower-http might provide a metrics middleware: https://github.com/tower-rs/tower-http/issues/57 +pub async fn track_metrics(req: Request, next: Next)->impl IntoResponse { + let start = Instant::now(); + + let path = if let Some(matched_path) = req.extensions().get::() { + matched_path.as_str().to_owned() + } else { + req.uri().path().to_owned() + }; + + let method = req.method().clone(); + + // Run the rest of the request handling first, so we can measure it and get response + // codes. + let response = next.run(req).await; + + let latency = start.elapsed().as_secs_f64(); + let status = response.status().as_u16().to_string(); + + let labels = [ + ("method", method.to_string()), + ("path", path), + ("status", status), + ]; + + metrics::increment_counter!("http_requests_total", &labels); + metrics::histogram!("http_requests_duration_seconds", latency, &labels); + + response +} + +``` + +Then, wherever you setup your Axum router, plug in the `/metrics` route! You'll need to make sure it's not publicly available. + +> [!note] +> +> I learned about `std::future::ready` here! It basically creates a future that is immediately available with a value. For example: +> +> ```rust +>let f = std::future::ready(1); +> assert_eq!(a.await, 1); +> ``` + + +```rust +let recorder_handle = setup_metrics_recorder(); + +let router = Router::new() + .route("/metrics", get(move || ready(recorder_handle.render()))) + .layer(axum::middleware::from_fn(track_metrics)); + +``` + +That's pretty much it really! \ No newline at end of file diff --git a/content/programming/geospatial queries with mongodb.md b/content/programming/geospatial queries with mongodb.md new file mode 100644 index 00000000..7acf105d --- /dev/null +++ b/content/programming/geospatial queries with mongodb.md @@ -0,0 +1,63 @@ +--- +title: Geospatial queries with MongoDB +date: 2019-07-25 +permalink: programming/geospatial-queries-with-mongodb +tags: + - python + - til +--- +I'm currently playing with MongoDB and its [geospatial queries](https://docs.mongodb.com/manual/geospatial-queries/?ref=ellie.wtf). It’s pretty interesting so far, so I just thought I'd write something up to show how I'm using it with PyMongo! + +Firstly, the obvious `pip install pymongo` is needed. We will need a mongo client first, which is easy enough + +```python +from pymongo import MongoClient + +client = MongoClient() +``` + +It’s pretty neat - by default, MongoClient will connect to `mongodb://localhost:27017`. + +You might want to do something like this instead though + +```python +url = os.environ.get("MONGO_URL", "mongodb://localhost:27017") +client = MongoClient(url) +``` + +We're going to want to create a MongoDB index on a document field to allow the geospatial magic to work. We will be creating a [`2dsphere`](https://docs.mongodb.com/manual/core/2dsphere/?ref=ellie.wtf) index. + +```python +from pymongo import MongoClient, GEOSPHERE, DESCENDING + +client = MongoClient() +db = client.foo +db.bar.create_index([("location", GEOSPHERE)]) +``` + +Now all that is needed is to insert a document and run a query :) We'll insert a document that just has a `location` field + +```python +db.bar.insert_one({ + “location”: { + "coordinates" : [ + 51.4982563, + -0.0861183 + ], + "type" : "Point" + } +}) +``` + +The `location` field needs to be [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON?ref=ellie.wtf), which is a JSON standard for representing geographic features. It makes querying for documents super easy! + +```python +db.bar.find({ + "location": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [LAT, LONG]}, + "$maxDistance": range, + } + } +}) +``` \ No newline at end of file diff --git a/content/programming/index.md b/content/programming/index.md new file mode 100644 index 00000000..bb5f8919 --- /dev/null +++ b/content/programming/index.md @@ -0,0 +1,6 @@ +--- +title: Programming +date: 2023-09-01 +--- + +A collection of programming notes. Some are regular-updated snippets (labeled evergreen), while others are more of a one-off (labeled til) \ No newline at end of file diff --git a/content/programming/python.md b/content/programming/python.md new file mode 100644 index 00000000..c1226904 --- /dev/null +++ b/content/programming/python.md @@ -0,0 +1,33 @@ +--- +permalink: programming/python +tags: + - python + - evergreen +title: Python snippets +date: 2023-09-01 +--- +I don't spend as much time building actual large projects in Python any more (though I was paid to write Python for a few years in my early career). These days it's mostly just for random glue scripts on a variety of systems. + +### Quick s3 client +Setup an S3 client, with credentials loaded from the env. You actually don't need to explicitly list the creds, so long as you set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. Magic ✨ + +``` +import boto3 + +s3 = boto3.resource('s3') + +for bucket in s3.buckets.all(): + print(bucket.name) + +``` + +### A generator to yield chunks of a fixed size + +```python +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i:i + n] +``` + +From [StackOverflow](https://stackoverflow.com/questions/312443/how-do-i-split-a-list-into-equally-sized-chunks) \ No newline at end of file diff --git a/content/programming/rest apis and swiftui.md b/content/programming/rest apis and swiftui.md new file mode 100644 index 00000000..dfbacb03 --- /dev/null +++ b/content/programming/rest apis and swiftui.md @@ -0,0 +1,57 @@ +--- +date: 2020-02-23 +permalink: programming/rest-apis-and-swiftui +tags: + - swift +title: Rest APIs and SwiftUI +--- + +I recently had someone ask me how I handled fetching and rendering data from a REST API in a SwiftUI view. + +If you're coming from something like React, it's actually handled differently. We can't just fire off a request in `init` or `onAppear`, and update some `@State`. Awkwardness with `escaping` closures, `struct`s not being mutable, etc etc etc. + +Luckily SwiftUI has some constructs that can help :) I won't be going over how to make GET requests or whatever in detail, though I like to use [Just](https://github.com/dduan/Just?ref=ellie.wtf). + +Firsts up, let's have a view + +```swift +struct UserProfile: View { + @ObservedObject user: User = User(username: "elm", name: "Ellie") + + var body: some View { + VStack { + Text("Username: \(self.user.username)") + Text("Name: \(self.user.name)") + } + } +} +``` + +So, how do we get the `user` object here loaded? `@State`? + +Actually no. There's something called an [ObservableObject](https://developer.apple.com/documentation/combine/observableobject?ref=ellie.wtf). This is an object with `@Published` properties. If a `@Published` property is updated, any view containing this object will have an update triggered. + +An example will probably illustrate this more effectively + +``` +struct User: ObservableObject { + @Published var username: String + @Published var name: String + + init(username: String, name: String) { + self.username = username + self.name = name + + self.load() + } + + func load() { + somehowRequestDataAsynchronously(); + // so basically just change `username` or `name` in here + } +} +``` + +The view essentially "observes" something "observable" - `@ObservedObject` and `@ObservableObject`. As soon as a `@Published` property is updated on something we are observing, a re-render is triggered and the view updates! + +Hopefully this clears things up! \ No newline at end of file diff --git a/content/programming/rust.md b/content/programming/rust.md new file mode 100644 index 00000000..9d8c67f4 --- /dev/null +++ b/content/programming/rust.md @@ -0,0 +1,42 @@ +--- +permalink: programming/rust +tags: + - rust +title: Rust snippets +date: 2023-09-01 +--- +## Projects +- [Atuin](/projects/atuin) +## Cargo +### Run just integration tests +``` +cargo test --test '*' +``` + +### Run just unit tests + +``` +cargo test --lib --bins +``` + +## Code +## git sha as string in code +Useful for distribution, to report what revision was built. In `build.rs`: +```rust +use std::process::Command; +fn main() { + let output = Command::new("git").args(["rev-parse", "HEAD"]).output(); + + let sha = match output { + Ok(sha) => String::from_utf8(sha.stdout).unwrap(), + Err(_) => String::from("NO_GIT"), + }; + + println!("cargo:rustc-env=GIT_HASH={}", sha); +} +``` + +Usage: +```rust +const SHA: &str = env!("GIT_HASH"); +``` diff --git a/content/projects/Atuin.md b/content/projects/Atuin.md new file mode 100644 index 00000000..f1292680 --- /dev/null +++ b/content/projects/Atuin.md @@ -0,0 +1,16 @@ +--- +permalink: projects/atuin +title: Atuin, magical shell history +tags: + - rust + - tool +date: 2021-05-10 +--- + +[github.com/ellie/atuin](https://github.com/ellie/atuin) + +Atuin is my longest-running project and certainly the most successful (depending on your measure of success). It synchronises shell history between all of your machines, keeps an e2e encrypted backup, and has a nice terminal search UI. + +For years I have thought that shell history could be improved. I was often working across multiple machines, and searching for commands I had ran months prior. Why couldn't I have all the history I'd ever ran, on all machines, at all times? + +Atuin was written to try and solve that problem. I originally named it "shync", from shell and sync, but luckily managed to choose something better. diff --git a/content/projects/Keyboard.md b/content/projects/Keyboard.md new file mode 100644 index 00000000..e0bd8433 --- /dev/null +++ b/content/projects/Keyboard.md @@ -0,0 +1,43 @@ +--- +permalink: projects/keyboard +date: 2020-02-26 +cover: https://img.ellie.wtf/i/1a4023009b55738bd5c8a9dbad4b855d7168308ff7782668a5df928e3ea7d0bf.jpg +title: I built a new keyboard +--- +![](https://img.ellie.wtf/i/1a4023009b55738bd5c8a9dbad4b855d7168308ff7782668a5df928e3ea7d0bf.jpg) + +Today the final components arrived for my keyboard build! I've been waiting on these for quite a long time, as some were custom made + all came from abroad. This isn't actually my first Planck build - the first was lost to a spilled energy drink. + +If you're new to mechanical keyboards, a Planck is a "40%" keyboard. It is very small, with only 48 keys. The lack of keys is made up for by having extra layers! For example, the "q" key can function as a "q", a "Q", a "!" and a "1". That's shift+q, lower+q, and raise+q. + +It was originally designed by [Jack Humbert](https://jackhumbert.com/?ref=ellie.wtf), and you can buy components from [OLKB](https://olkb.com/?ref=ellie.wtf) (many places stock components though). If the Planck is too small for you, there's a Preonic as well - it's a little larger. Other than the size, the other selling point is that all the keys are arranged in a grid. I guess this is either something you love, or something that you hate. I actually tried an [ErgoDox](https://ergodox-ez.com/?ref=ellie.wtf) for a while, but I think that the Ortholinear layout is something that I prefer. + +It runs the [QMK](https://docs.qmk.fm/?ref=ellie.wtf#/) firmware, and is totally reflashable. So, if you want a different layout, or some other functionality that doesn't yet exist, it's easy. So long as you can write C, anyway + +![](https://img.ellie.wtf/i/a390afc8637ab00116b760c49c7a2800877fb34cf892d727c71d1d4234cb4c16.jpg) + +The first thing I had arrive was the PCB. I ordered it from [UK Keycaps](http://www.ukkeycaps.co.uk/?ref=ellie.wtf), as it was in stock and had fast shipping! + +After that was the case/box. That came from [Datamancer](https://datamancer.com/product/datamancer-magnetic-clamshell-planck-hardwood-keyboard-case-rev-6/?ref=ellie.wtf), and is made of Walnut. I went for the taller case, as I like SA-profile keycaps. It took a little while to arrive, but I'm very very happy with the quality. The bottom and top are lined with magnets on the inner edges, so the lid "snaps" into place! It also dual functions as a wrist rest, thanks to some more magnets. + +![](https://img.ellie.wtf/i/ad40be4f072aa231df11673fe4bf04a81cbba192aaf0ed9953012a5dc81bcc24.jpg) + +Otherwise, I went for [Halo True](https://drop.com/buy/massdrop-halo-switch-pack?ref=ellie.wtf) switches. This was a bit of a gamble, as I've never used Topre switches (supposedly similar) and I normally go for either MX Clears, Zealio, or something similar. I love them! + +I was very tempted to order something nice and clicky, but seeing as I'm planning on carrying this board around with me (hence the size), I didn't think people in coffee shops would appreciate the clicky-clacky as much as I do. + +The cool thing with the latest Planck PCB, is that the switches are hot swappable. Maybe sometime in the future I'll want to try something new, and that's totally doable and wouldn't even take very long. + +I also needed a top plate. Originally I was going to order from OLKB, but there's a pretty large waiting list/delay over there. I was also considering getting one made from something other than steel. + +I ended up ordering from [Laserboost](https://www.laserboost.com/?ref=ellie.wtf), and went for a shiny brass plate. I was pretty torn between brass and copper, but definitely don't regret my decision + +![](https://img.ellie.wtf/i/ad6eaab6b2dbe4eca83d7bb12d73bea5774c5e406ab7e9c0e7a2ade06995d373.jpg) + +Finally... the keycaps! I've ordered from [Pimp My Keyboard](https://pimpmykeyboard.com/?ref=ellie.wtf) before and been very happy, so I thought I'd do so again. I ended up going for [SA 1976](https://pimpmykeyboard.com/sa-1976-keyset/?ref=ellie.wtf), and I'm very happy with my choice. The keys feel solid, I like the colours, and I think it matches the walnut finish of the case pretty well too. + +I actually use this on top of my laptop keyboard, with the internal keyboard disabled. That way I'm not tethered to a desk, but I still get a nice keyboard :D + +![](https://img.ellie.wtf/i/b9f1eeae8c35ce05660db6aea70b2ad469a2f1d7d52ce2691ece0cf65bf0e2e0.jpg) + +If you have any questions for me, or just want to say hi, I'm [@ellie_huxtable](https://twitter.com/ellie_huxtable?ref=ellie.wtf) on Twitter! Otherwise, "ellie at elliehuxtable DOT COM" \ No newline at end of file diff --git a/content/projects/iPod.md b/content/projects/iPod.md new file mode 100644 index 00000000..475d8446 --- /dev/null +++ b/content/projects/iPod.md @@ -0,0 +1,102 @@ +--- +permalink: projects/ipod +cover: https://img.ellie.wtf/i/61809d8afda86035bc05e3b8b97c34fc7ad7b5d3f41978d1a1f38d0b1d2aa45f.jpg +title: An iPod for 2022 +date: 2022-02-14 +--- +![](https://img.ellie.wtf/i/61809d8afda86035bc05e3b8b97c34fc7ad7b5d3f41978d1a1f38d0b1d2aa45f.jpg) + +> [!info] Press +> This also ended up getting picked up by [Vice](https://www.vice.com/en/article/qjbexd/a-software-engineer-upgraded-an-old-ipod-for-2022?ref=ellie.wtf), [Hackaday](https://hackaday.com/2022/02/16/classic-ipods-are-super-upgradeable-in-2022/?ref=ellie.wtf), [Techspot](https://www.techspot.com/community/topics/breathing-new-life-into-an-old-ipod-with-a-few-thoughtful-upgrades.273895/?ref=ellie.wtf), as well as a whole bunch of other publications! Thank you so much for your interest in my work 💖 + +I shared this on Twitter recently, and have had a bunch of people ask me for more details - so here they are! + +![](https://twitter.com/ellie_huxtable/status/1492989732002877443) + +Just a heads up that this is pretty image-heavy, in case you're on a limited data plan. + +I've always been the sort of person who struggles to do anything without some sort of music going on in the background. A long time ago this was with a little mp3 player, then an iPod, and eventually on to a phone with Spotify. + +I realised something not so long ago - I was being very lazy. I'd often just play my weekly/daily mix, or some playlist I made up a long time ago. I'd never really think about what music I liked + what music I wanted to listen to. I think this is in part due to the fact that almost any music was available - which made choosing even more difficult. +Anyway. Over the weekend I took apart a 5.5th gen iPod Classic (or iPod Video) and made it suit 2022 a little better :D + +I won't be detailing precisely how I did it. I mostly followed [iFixit](https://www.ifixit.com/Device/iPod_5th_Generation_%28Video%29?ref=ellie.wtf), and fumbled my way through the rest. Almost all of the cables can be removed by lifting a little black lever, and are equally as easy to replace. I'd recommend ordering some iPod tools :) + +![](https://img.ellie.wtf/i/7132cb66d39602d2741da6ade98d86c01a16c8e6dcc33c26387145da711afeef.jpg) + +# The iPod + +I went with a 5.5 for a few reasons. Firstly, the front is plastic. I was going to be replacing the front anyway, so it might as well be easy to open! + +Secondly, and most importantly - the DAC. This generation of iPod was the last to feature the "wolfson DAC", which is very well known for being [of fantastic quality](https://www.macintoshhowto.com/ipod/which-ipod-has-the-best-audio-quality.html/2?ref=ellie.wtf). + +Something else to be aware of is that different models of this iPod had different amounts of memory. The 80GB has 64mb of memory, while smaller capacities have only 32mb. I went with the 80GB :) + +# The components + +I ordered a bunch of components for this, as I knew pretty much exactly what I wanted. The vast majority of them were found on eBay, where there is a LOT of choice. If there's anything specific, I'll link it + +## Opening + +Opening the iPod was made fairly simple with the iPod tools. After working my way around the casing, it just popped open. iFixit had good instructions there! + +![](https://img.ellie.wtf/i/172fbb1d32d2f1d61f483813e01d11105bb78aa293487dd841a68e604b822cdd.jpg) +## Storage + +First and foremost, storage. 80GB is definitely not enough for what I want. The original iPod also used a HDD - ideally I'd be swapping this out for something solid state. + +![](https://img.ellie.wtf/i/fafb72c71690e71291140105db0fb7ae5c7e56c1c270874f758386766b53ce91.jpg) + +There's a lot of choice here, but I went with an [iFlash Quad](https://www.iflash.xyz/store/iflash-quad/?ref=ellie.wtf). This lets you use up to four SD cards! There's no real performance difference here compared to other storage options, as it's pretty limited regardless. In addition, SD cards use less power + put out less heat than the comparable SSDs. + +![](https://img.ellie.wtf/i/c458772fca92497b4d10740748a36abc645066a2c7b629a45ed015615d67a346.jpg) + +This is also thinner than the HDD, allowing you to fit a larger battery or other mods into the chassis. + +Something to be aware of is that the original iPod firmware can struggle with the larger capacity drives, so you will probably need something like [Rockbox](https://www.rockbox.org/?ref=ellie.wtf). Bear in mind that Rockbox can have issues with the iFlash hardware, so you may need to run a daily build (or transfer your music while [booted into the original firmware](https://www.rockbox.org/wiki/Main/IpodFAQ?ref=ellie.wtf#How_do_I_start_the_original_Apple_Firmware_63)) + +## Battery + +You can find a LOT of batteries on eBay and other places online, so I won't really say much about that here. I went with a 3000mah :) + +![](https://img.ellie.wtf/i/84deb9a76972e7ed15e62bb68e1fc5a1abe97772cc4ac0df8ec1af0b93ec5b68.jpg) + +The battery is one of the first things you need to unplug, as otherwise you can't fully open the iPod. Attemping to open without unplugging will result in broken cables! + +![](https://img.ellie.wtf/i/ee10948f7ed2584413cab577d197714348f863788c64563d76dc7868911de399.jpg) +## Front casing + +I bought a clear front casing for mine, as I like seeing the guts of my gadgets! Installing this was more fiddly than I expected. Once taken apart, there were six super tiny screws along the edge of the chassis that needed removing. From there, the front comes off - the iPod scroll wheel is actually just resting in place. So if you're not careful, it kinda flops about! As does the display + +![](https://img.ellie.wtf/i/6c11cb0c1adf8ec0e92c8156e8be54590ac41f94a53acc87c4a642c2625d8532.jpg) + +![](https://img.ellie.wtf/i/ceeaee98f04689b6fca27e2604b98663ddc8e37ffff7e4026757fbcb41e6d7b5.jpg) +## Rear casing +A small detail I wanted to change was the capacity written on the back. After all, it's not 80GB any more. Also found on eBay, I bought a new rear casing + +![](https://img.ellie.wtf/i/5d07e9739788d56cb152f76c3c9b8783c05fe78373c956a144e85e1d3dbd9372.jpg) + + +The issue with this is that the headphone jack and hold switch also needed transferring across. This wasn't too difficult in the end, though a little fiddly + I had to be very careful with the cables as they are fragile + +![](https://img.ellie.wtf/i/13f82968923a66a81656f04e8c15ef4cc7301faecf87a0d1c14662b5c49db6d8.jpg) + +# Moment of truth +After I'd put everything back together, I was worried it'd not boot. Or that it would boot, but input/sound wouldn't work. + +Switching it on resulted in this screen: + +![](https://img.ellie.wtf/i/4f791d0ca385899285662c639fb68d20d04ee4e7a4f3c835e9ff06f183081fd9.jpg) + + +So, I restored it on iTunes, and it worked! I followed up by installing [Rockbox](https://rockbox.org/?ref=ellie.wtf), and then the theme called "[FreshOS](https://forums.rockbox.org/index.php?topic=53574.0&ref=ellie.wtf)" which gives it a nice clean look (in my opinion). Rockbox has a nice installer which makes this super easy. + +In addition, I no longer need to use iTunes! The iPod mounts as external storage, and the files can just be copied across. Easy. + +Rockbox also means I can [play doom](https://twitter.com/ellie_huxtable/status/1493172771790245890?s=20&t=C9L2RYcRdfT7aPmvmQqJmw&ref=ellie.wtf), or [control my Macbooks volume from the iPod](https://twitter.com/ellie_huxtable/status/1492989855101509634?s=20&t=C9L2RYcRdfT7aPmvmQqJmw&ref=ellie.wtf) (you know, a very useful thing to do...) + +Anyway, if you have any questions please do feel free to get in touch on [Twitter](https://twitter.com/ellie_huxtable?ref=ellie.wtf)! + +![](https://img.ellie.wtf/i/16f835dd148da5bc8b43df6eded00dcd58aa1f5437d4397dc6999b8dfd1507b7.jpg) + +![](https://img.ellie.wtf/i/108be2390b667272528efd7a917886ab99d309d10ee33ebdf2eeb6ecd039b4be.jpg) \ No newline at end of file diff --git a/content/projects/index.md b/content/projects/index.md new file mode 100644 index 00000000..29a8b59e --- /dev/null +++ b/content/projects/index.md @@ -0,0 +1,5 @@ +--- +title: Projects +date: 2023-09-01 +--- +I love making things! I write about them here. \ No newline at end of file diff --git a/content/projects/yeet a personal image host.md b/content/projects/yeet a personal image host.md new file mode 100644 index 00000000..5fd66b57 --- /dev/null +++ b/content/projects/yeet a personal image host.md @@ -0,0 +1,6 @@ +--- +permalink: projects/yeet +draft: true +--- + +- TLS failed when the S3 bucket had a `.` in the name. I forgot that this breaks SSL certificates! \ No newline at end of file diff --git a/content/publish.js b/content/publish.js new file mode 100644 index 00000000..fdf1e384 --- /dev/null +++ b/content/publish.js @@ -0,0 +1,5 @@ +var plausibleSnippet = document.createElement('script'); +plausibleSnippet.defer = true; +plausibleSnippet.setAttribute('data-domain', 'notes.ellie.wtf'); +plausibleSnippet.src = 'https://plausible.io/js/plausible.js'; +document.head.appendChild(plausibleSnippet); diff --git a/content/tools/Git.md b/content/tools/Git.md new file mode 100644 index 00000000..4cc29927 --- /dev/null +++ b/content/tools/Git.md @@ -0,0 +1,26 @@ +--- +permalink: tools/git +date: 2023-09-01 +--- +# Git + +## Remove untracked files +``` +git clean -fdx +``` + +- `-f` force +- `-d` include directories +- `-x` also remove ignored files + +## Disable push to master/main +It's pretty easy to accidentally push to main/master (if for some reason you cannot enable branch protection), so putting + +``` +[branch.main] + pushRemote = +[branch.master] + pushRemote = +``` + +in your git config sorts this out! If you would like to intentionally push to an important branch, `git push origin main` will sort you out. \ No newline at end of file diff --git a/content/tools/jq.md b/content/tools/jq.md new file mode 100644 index 00000000..c1121e7f --- /dev/null +++ b/content/tools/jq.md @@ -0,0 +1,20 @@ +--- +permalink: tools/jq +date: 2023-09-01 +--- +# jq + +Just a collection of jq things I've found useful + +## Arrays + +### Get an array element by index +``` +echo '[1, 2, 3, 4, 5]' | jq '.[0]' # => 1 +``` + +### Get the length of an array + +``` +echo '[1, 2, 3, 4, 5]' | jq 'length' # => 5 +``` \ No newline at end of file diff --git a/content/tools/mdbook.md b/content/tools/mdbook.md new file mode 100644 index 00000000..a9c1d78d --- /dev/null +++ b/content/tools/mdbook.md @@ -0,0 +1,49 @@ +--- +permalink: tools/mdbook +date: 2023-09-01 +--- +# mdbook + +I was trying out mdbook for this, but no longer use it. I'll keep the notes just in case. + +## Vercel deployments +I currently deploy static sites to vercel, because I'm lazy and don't want to spend _even more time_ doing infra. + +To get mdbook to deploy successfully: + +1. Set Framework Preset to "Other" +2. Set Output Directory to "book" +3. Set Build Command to: +``` +curl -Lo mdbook.tar.gz https://github.com/rust-lang/mdBook/releases/download/v0.4.32/mdbook-v0.4.32-x86_64-unknown-linux-musl.tar.gz; tar -xvzf mdbook.tar.gz; ./mdbook build +``` + +You'll probably want to update the version there. Downloading from the release page, we can deploy a wiki in 4s! + +Alternatively, you could build from source - though this will take minutes, not seconds: +``` +amazon-linux-extras install rust1; cargo install mdbook; mdbook build +``` + +The only upside I can see here is that it's automatically the newest version + +## Analytics +I use [Plausible](https://plausible.io). mdbook supports custom javascript, but only with a script file in your repo. Something like this does nicely: + +book.toml +```toml +[output.html] +additional-js = ["plausible.js"] +``` + +plausible.js +```javascript +const script = document.createElement('script'); + +script.src = "https://plausible.io/js/script.js"; + +script.defer = true; +script.setAttribute("data-domain", "YOUR DOMAIN HERE"); // you probs want to change this + +document.body.appendChild(script); +``` \ No newline at end of file diff --git a/quartz.config.ts b/quartz.config.ts index f677a18f..2680c2cc 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -3,13 +3,13 @@ import * as Plugin from "./quartz/plugins" const config: QuartzConfig = { configuration: { - pageTitle: "🪴 Quartz 4.0", + pageTitle: "👩‍💻 Ellie's Notes", enableSPA: true, enablePopovers: true, analytics: { provider: "plausible", }, - baseUrl: "quartz.jzhao.xyz", + baseUrl: "ellie.wtf", ignorePatterns: ["private", "templates", ".obsidian"], defaultDateType: "created", theme: { @@ -20,22 +20,22 @@ const config: QuartzConfig = { }, colors: { lightMode: { - light: "#faf8f8", - lightgray: "#e5e5e5", + light: "#f5f2e9", + lightgray: "#e0dcd3", gray: "#b8b8b8", darkgray: "#4e4e4e", dark: "#2b2b2b", - secondary: "#284b63", + secondary: "#384b73", tertiary: "#84a59d", highlight: "rgba(143, 159, 169, 0.15)", }, darkMode: { - light: "#161618", + light: "#1a1915", lightgray: "#393639", gray: "#646464", darkgray: "#d4d4d4", dark: "#ebebec", - secondary: "#7b97aa", + secondary: "#5d7da5", tertiary: "#84a59d", highlight: "rgba(143, 159, 169, 0.15)", }, diff --git a/quartz.layout.ts b/quartz.layout.ts index 482aba6e..42b25709 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -7,8 +7,9 @@ export const sharedPageComponents: SharedLayout = { header: [], footer: Component.Footer({ links: { - GitHub: "https://github.com/jackyzha0/quartz", - "Discord Community": "https://discord.gg/cRFFHYye7t", + GitHub: "https://github.com/ellie", + Mastodon: "https://hachyderm.io/@ellie", + Twitter: "https://twitter.com/ellie_huxtable", }, }), } @@ -21,9 +22,13 @@ export const defaultContentPageLayout: PageLayout = { Component.MobileOnly(Component.Spacer()), Component.Search(), Component.Darkmode(), + Component.DesktopOnly(Component.RecentNotes()), + ], + right: [ + Component.Graph(), Component.DesktopOnly(Component.TableOfContents()), + Component.Backlinks(), ], - right: [Component.Graph(), Component.Backlinks()], } // components for pages that display lists of pages (e.g. tags or folders) diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 2bf26381..db6976ea 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -30,6 +30,14 @@ export default (() => { + + {/* I could totally edit the below loop, but I'd rather keep my changes localized to make for easier rebasing on updates */} + + {css.map((href) => ( ))} diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss index b908314b..020d48b8 100644 --- a/quartz/styles/custom.scss +++ b/quartz/styles/custom.scss @@ -1 +1,24 @@ // put your custom CSS here! +p, li { + font-size: 1.2rem; + line-height: 1.5 !important; +} + + +@media (max-width: 480px) { + .me { + display: none; + } +} + +@media (min-width: 480px) { + .me { + width: 70%; + padding: 10px; + } + + .welcome { + display: flex; + align-items: center; + } +} From 7a440ac18c4194e114b872e2788c697f9a573fd9 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 22:18:48 +0100 Subject: [PATCH 02/94] Quartz sync: Sep 13, 2023, 10:18 PM --- content/life/2022.md | 2 +- ...t.md => lessons learned with swiftui and ios development.md} | 0 content/programming/rust.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename content/posts/{Lessons learned with Swift and iOS development.md => lessons learned with swiftui and ios development.md} (100%) diff --git a/content/life/2022.md b/content/life/2022.md index 76e42897..cc666953 100644 --- a/content/life/2022.md +++ b/content/life/2022.md @@ -18,7 +18,7 @@ This is another photo-heavy post from me, as I'm telling a story and that's best After visiting the design museum in London and seeing some old iPods, I was inspired to have a go at [modding one](ipod.md). I did this in a couple of hours after spending the day at a motorcycle show, posted some photos online, and it really took off. First a lot of attention on Twitter; then a bunch in the press - including a small interview and feature with [Vice](https://www.vice.com/en/article/qjbexd/a-software-engineer-upgraded-an-old-ipod-for-2022?ref=ellie.wtf). ## Atuin -This year I continued to work on [Atuin](/projects/atuin) - albeit fairly slowly. We had a couple of magazine features (which I still can't believe), and gained even more contributors! Thank you to everyone that has helped make Atuin better <3 +This year I continued to work on [Atuin](atuin.md) - albeit fairly slowly. We had a couple of magazine features (which I still can't believe), and gained even more contributors! Thank you to everyone that has helped make Atuin better <3 I also opened up a GitHub sponsors. This was for two reasons really. Firstly, I was paying for a server to host the public sync instance, and wanted to reduce the financial burden. Secondly, I want to try and ensure Atuin has a sustainable future. So far this is paying for the server bills, with a little left over! I never thought this would happen, thanks everyone 💖 diff --git a/content/posts/Lessons learned with Swift and iOS development.md b/content/posts/lessons learned with swiftui and ios development.md similarity index 100% rename from content/posts/Lessons learned with Swift and iOS development.md rename to content/posts/lessons learned with swiftui and ios development.md diff --git a/content/programming/rust.md b/content/programming/rust.md index 9d8c67f4..75f2b7d0 100644 --- a/content/programming/rust.md +++ b/content/programming/rust.md @@ -6,7 +6,7 @@ title: Rust snippets date: 2023-09-01 --- ## Projects -- [Atuin](/projects/atuin) +- [Atuin](atuin.md) ## Cargo ### Run just integration tests ``` From eee07d267157fd3589669dba7e9482cf10b1abbf Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 22:42:13 +0100 Subject: [PATCH 03/94] Quartz sync: Sep 13, 2023, 10:42 PM --- content/index.md | 4 ++-- content/infra/{Alertmanager.md => alertmanager.md} | 0 .../infra/{Kube State Metrics.md => kube state metrics.md} | 0 content/infra/{Kubernetes.md => kubernetes.md} | 0 content/infra/{Linux Filesystems.md => linux filesystems.md} | 0 content/infra/{Networking.md => networking.md} | 0 content/infra/{Postgres.md => postgres.md} | 0 content/infra/{SQLite.md => sqlite.md} | 0 content/infra/{Systemd.md => systemd.md} | 0 ...ies with MongoDB.md => geospatial queries with mongodb.md} | 0 ...nt to get shit done.md => i just want to get shit done.md} | 0 content/posts/{My backup script.md => my backup script.md} | 0 content/posts/{Personal wiki.md => personal wiki.md} | 0 .../{Rest APIs and SwiftUI.md => rest apis and swiftui.md} | 0 content/programming/rust.md | 4 ++-- content/projects/{Atuin.md => atuin.md} | 0 content/projects/{iPod.md => ipod.md} | 0 content/projects/{Keyboard.md => keyboard.md} | 0 content/tools/{Git.md => git.md} | 0 19 files changed, 4 insertions(+), 4 deletions(-) rename content/infra/{Alertmanager.md => alertmanager.md} (100%) rename content/infra/{Kube State Metrics.md => kube state metrics.md} (100%) rename content/infra/{Kubernetes.md => kubernetes.md} (100%) rename content/infra/{Linux Filesystems.md => linux filesystems.md} (100%) rename content/infra/{Networking.md => networking.md} (100%) rename content/infra/{Postgres.md => postgres.md} (100%) rename content/infra/{SQLite.md => sqlite.md} (100%) rename content/infra/{Systemd.md => systemd.md} (100%) rename content/posts/{Geospatial queries with MongoDB.md => geospatial queries with mongodb.md} (100%) rename content/posts/{I just want to get shit done.md => i just want to get shit done.md} (100%) rename content/posts/{My backup script.md => my backup script.md} (100%) rename content/posts/{Personal wiki.md => personal wiki.md} (100%) rename content/posts/{Rest APIs and SwiftUI.md => rest apis and swiftui.md} (100%) rename content/projects/{Atuin.md => atuin.md} (100%) rename content/projects/{iPod.md => ipod.md} (100%) rename content/projects/{Keyboard.md => keyboard.md} (100%) rename content/tools/{Git.md => git.md} (100%) diff --git a/content/index.md b/content/index.md index e27c00dc..a27e7234 100644 --- a/content/index.md +++ b/content/index.md @@ -1,6 +1,6 @@ --- title: Ellie's Notes -date: 2023-09-01 +date: 0202-09-01 --- @@ -23,7 +23,7 @@ Here I am trying to maintain a personal wiki, or a second brain. I often This site is constantly shifting, but here are some things you may be interested in - [Posts](/posts), a selection of my longer-form writing and thoughts -- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod]] and [[atuin]]. +- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod]] and [[atuin | Atuin]]. - [Life](/life), where I'm writing about my travels, adventures, and life in general - I also keep some notes on [programming](/programming), [infra](/infra), and [tools](/tools). Have an explore! diff --git a/content/infra/Alertmanager.md b/content/infra/alertmanager.md similarity index 100% rename from content/infra/Alertmanager.md rename to content/infra/alertmanager.md diff --git a/content/infra/Kube State Metrics.md b/content/infra/kube state metrics.md similarity index 100% rename from content/infra/Kube State Metrics.md rename to content/infra/kube state metrics.md diff --git a/content/infra/Kubernetes.md b/content/infra/kubernetes.md similarity index 100% rename from content/infra/Kubernetes.md rename to content/infra/kubernetes.md diff --git a/content/infra/Linux Filesystems.md b/content/infra/linux filesystems.md similarity index 100% rename from content/infra/Linux Filesystems.md rename to content/infra/linux filesystems.md diff --git a/content/infra/Networking.md b/content/infra/networking.md similarity index 100% rename from content/infra/Networking.md rename to content/infra/networking.md diff --git a/content/infra/Postgres.md b/content/infra/postgres.md similarity index 100% rename from content/infra/Postgres.md rename to content/infra/postgres.md diff --git a/content/infra/SQLite.md b/content/infra/sqlite.md similarity index 100% rename from content/infra/SQLite.md rename to content/infra/sqlite.md diff --git a/content/infra/Systemd.md b/content/infra/systemd.md similarity index 100% rename from content/infra/Systemd.md rename to content/infra/systemd.md diff --git a/content/posts/Geospatial queries with MongoDB.md b/content/posts/geospatial queries with mongodb.md similarity index 100% rename from content/posts/Geospatial queries with MongoDB.md rename to content/posts/geospatial queries with mongodb.md diff --git a/content/posts/I just want to get shit done.md b/content/posts/i just want to get shit done.md similarity index 100% rename from content/posts/I just want to get shit done.md rename to content/posts/i just want to get shit done.md diff --git a/content/posts/My backup script.md b/content/posts/my backup script.md similarity index 100% rename from content/posts/My backup script.md rename to content/posts/my backup script.md diff --git a/content/posts/Personal wiki.md b/content/posts/personal wiki.md similarity index 100% rename from content/posts/Personal wiki.md rename to content/posts/personal wiki.md diff --git a/content/posts/Rest APIs and SwiftUI.md b/content/posts/rest apis and swiftui.md similarity index 100% rename from content/posts/Rest APIs and SwiftUI.md rename to content/posts/rest apis and swiftui.md diff --git a/content/programming/rust.md b/content/programming/rust.md index 75f2b7d0..9a08fd80 100644 --- a/content/programming/rust.md +++ b/content/programming/rust.md @@ -2,11 +2,11 @@ permalink: programming/rust tags: - rust -title: Rust snippets +title: Rust date: 2023-09-01 --- ## Projects -- [Atuin](atuin.md) +- [[atuin | Atuin]] ## Cargo ### Run just integration tests ``` diff --git a/content/projects/Atuin.md b/content/projects/atuin.md similarity index 100% rename from content/projects/Atuin.md rename to content/projects/atuin.md diff --git a/content/projects/iPod.md b/content/projects/ipod.md similarity index 100% rename from content/projects/iPod.md rename to content/projects/ipod.md diff --git a/content/projects/Keyboard.md b/content/projects/keyboard.md similarity index 100% rename from content/projects/Keyboard.md rename to content/projects/keyboard.md diff --git a/content/tools/Git.md b/content/tools/git.md similarity index 100% rename from content/tools/Git.md rename to content/tools/git.md From ac55043e043188c1dd9188ed09593e845e6bbd7f Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 22:49:29 +0100 Subject: [PATCH 04/94] Quartz sync: Sep 13, 2023, 10:49 PM --- content/index.md | 4 ++-- content/infra/alertmanager.md | 1 + content/infra/kube state metrics.md | 1 + content/infra/kubernetes.md | 1 + content/infra/linux filesystems.md | 1 + content/infra/networking.md | 1 + content/infra/postgres.md | 1 + content/infra/sqlite.md | 1 + content/infra/systemd.md | 1 + content/projects/yeet a personal image host.md | 2 +- 10 files changed, 11 insertions(+), 3 deletions(-) diff --git a/content/index.md b/content/index.md index a27e7234..9b0bcbdc 100644 --- a/content/index.md +++ b/content/index.md @@ -9,7 +9,7 @@ date: 0202-09-01 # 👋 Welcome! -My full name is Ellie Huxtable. I'm a software/infrastructure engineer, and am at my happiest when I'm building something cool. I love an adventure, and if I'm not at a computer there's a good chance I'm riding a motorcycle. +My name is Ellie Huxtable. I'm a software/infrastructure engineer, and am at my happiest when I'm building something cool. I love an adventure, and if I'm not at a computer there's a good chance I'm riding a motorcycle. Here I am trying to maintain a personal wiki, or a second brain. I often explore many technologies or ideas, and then promptly completely forget them. I've found that writing my learnings and thoughts down is immensely helpful. @@ -23,7 +23,7 @@ Here I am trying to maintain a personal wiki, or a second brain. I often This site is constantly shifting, but here are some things you may be interested in - [Posts](/posts), a selection of my longer-form writing and thoughts -- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod]] and [[atuin | Atuin]]. +- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod | iPod]] and [[atuin | Atuin]]. - [Life](/life), where I'm writing about my travels, adventures, and life in general - I also keep some notes on [programming](/programming), [infra](/infra), and [tools](/tools). Have an explore! diff --git a/content/infra/alertmanager.md b/content/infra/alertmanager.md index 99a6b6a9..39b79490 100644 --- a/content/infra/alertmanager.md +++ b/content/infra/alertmanager.md @@ -1,6 +1,7 @@ --- permalink: infra/alertmanager date: 2023-09-01 +title: Alertmanager --- # Alertmanager diff --git a/content/infra/kube state metrics.md b/content/infra/kube state metrics.md index 015b0f2c..d096aecd 100644 --- a/content/infra/kube state metrics.md +++ b/content/infra/kube state metrics.md @@ -1,6 +1,7 @@ --- permalink: infra/kube-state-metrics date: 2023-09-01 +title: Kube State Metrics --- # Kube State Metrics diff --git a/content/infra/kubernetes.md b/content/infra/kubernetes.md index 1121811b..14d1edb3 100644 --- a/content/infra/kubernetes.md +++ b/content/infra/kubernetes.md @@ -1,6 +1,7 @@ --- permalink: infra/kubernetes date: 2023-09-01 +title: Kubernetes --- # Kubernetes diff --git a/content/infra/linux filesystems.md b/content/infra/linux filesystems.md index 7d5b92ff..5370c14c 100644 --- a/content/infra/linux filesystems.md +++ b/content/infra/linux filesystems.md @@ -1,6 +1,7 @@ --- permalink: infra/filesystems date: 2023-09-01 +title: Linux Filesystems --- Just some Linux-y notes diff --git a/content/infra/networking.md b/content/infra/networking.md index c4c48289..b6e7fb42 100644 --- a/content/infra/networking.md +++ b/content/infra/networking.md @@ -1,6 +1,7 @@ --- permalink: infra/networking date: 2023-09-01 +title: Networking --- # Networking diff --git a/content/infra/postgres.md b/content/infra/postgres.md index 4682fd4b..3c78d09e 100644 --- a/content/infra/postgres.md +++ b/content/infra/postgres.md @@ -1,6 +1,7 @@ --- permalink: infra/postgres date: 2023-09-01 +title: Postgres --- # PostgreSQL diff --git a/content/infra/sqlite.md b/content/infra/sqlite.md index 1fb3f364..3e656191 100644 --- a/content/infra/sqlite.md +++ b/content/infra/sqlite.md @@ -1,6 +1,7 @@ --- permalink: infra/sqlite date: 2023-09-01 +title: SQLite --- # SQLite diff --git a/content/infra/systemd.md b/content/infra/systemd.md index a3f0834e..0723ccfa 100644 --- a/content/infra/systemd.md +++ b/content/infra/systemd.md @@ -1,6 +1,7 @@ --- permalink: infra/systemd date: 2023-09-01 +title: Systemd --- # Systemd diff --git a/content/projects/yeet a personal image host.md b/content/projects/yeet a personal image host.md index 5fd66b57..445a92ee 100644 --- a/content/projects/yeet a personal image host.md +++ b/content/projects/yeet a personal image host.md @@ -2,5 +2,5 @@ permalink: projects/yeet draft: true --- - +- Written in [[rust | Rust]] - TLS failed when the S3 bucket had a `.` in the name. I forgot that this breaks SSL certificates! \ No newline at end of file From bcb1a6b5fd64272203e2c167c55f5b81c036c7e1 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 23:05:21 +0100 Subject: [PATCH 05/94] Hide meta on index pages --- quartz/components/ContentMeta.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/quartz/components/ContentMeta.tsx b/quartz/components/ContentMeta.tsx index 3e1b7011..616da2d3 100644 --- a/quartz/components/ContentMeta.tsx +++ b/quartz/components/ContentMeta.tsx @@ -5,6 +5,11 @@ import readingTime from "reading-time" export default (() => { function ContentMetadata({ cfg, fileData }: QuartzComponentProps) { const text = fileData.text + + if (fileData.slug === "index") { + return null + } + if (text) { const segments: string[] = [] const { text: timeTaken, words: _words } = readingTime(text) From 3c7fb7c72adffc73907ae7433133e2fcffa60a8e Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 23:07:48 +0100 Subject: [PATCH 06/94] Quartz sync: Sep 13, 2023, 11:07 PM --- content/index.md | 5 +---- content/programming/python.md | 2 +- content/programming/rust.md | 1 + 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/content/index.md b/content/index.md index 9b0bcbdc..6a6e7976 100644 --- a/content/index.md +++ b/content/index.md @@ -1,14 +1,11 @@ --- -title: Ellie's Notes +title: Welcome! 👋 date: 0202-09-01 --- -
-# 👋 Welcome! - My name is Ellie Huxtable. I'm a software/infrastructure engineer, and am at my happiest when I'm building something cool. I love an adventure, and if I'm not at a computer there's a good chance I'm riding a motorcycle. Here I am trying to maintain a personal wiki, or a second brain. I often explore many technologies or ideas, and then promptly completely forget them. I've found that writing my learnings and thoughts down is immensely helpful. diff --git a/content/programming/python.md b/content/programming/python.md index c1226904..2cced3af 100644 --- a/content/programming/python.md +++ b/content/programming/python.md @@ -3,7 +3,7 @@ permalink: programming/python tags: - python - evergreen -title: Python snippets +title: Python date: 2023-09-01 --- I don't spend as much time building actual large projects in Python any more (though I was paid to write Python for a few years in my early career). These days it's mostly just for random glue scripts on a variety of systems. diff --git a/content/programming/rust.md b/content/programming/rust.md index 9a08fd80..87ada1a5 100644 --- a/content/programming/rust.md +++ b/content/programming/rust.md @@ -2,6 +2,7 @@ permalink: programming/rust tags: - rust + - evergreen title: Rust date: 2023-09-01 --- From 57bdec0d5d377875fa252c174a7d334c5ea8e5fa Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 23:10:14 +0100 Subject: [PATCH 07/94] Quartz sync: Sep 13, 2023, 11:10 PM --- content/tools/git.md | 2 ++ content/tools/jq.md | 2 ++ content/tools/mdbook.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/content/tools/git.md b/content/tools/git.md index 4cc29927..0fc7aec0 100644 --- a/content/tools/git.md +++ b/content/tools/git.md @@ -1,6 +1,8 @@ --- permalink: tools/git date: 2023-09-01 +tags: + - tool --- # Git diff --git a/content/tools/jq.md b/content/tools/jq.md index c1121e7f..bc4e78f4 100644 --- a/content/tools/jq.md +++ b/content/tools/jq.md @@ -1,6 +1,8 @@ --- permalink: tools/jq date: 2023-09-01 +tags: + - tool --- # jq diff --git a/content/tools/mdbook.md b/content/tools/mdbook.md index a9c1d78d..112f9946 100644 --- a/content/tools/mdbook.md +++ b/content/tools/mdbook.md @@ -1,6 +1,8 @@ --- permalink: tools/mdbook date: 2023-09-01 +tags: + - tool --- # mdbook From ca1c9e01902130e5708dcba8431630fad3fdeea5 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 13 Sep 2023 23:16:30 +0100 Subject: [PATCH 08/94] Quartz sync: Sep 13, 2023, 11:16 PM --- content/index.md | 2 +- quartz/static/icon.png | Bin 17368 -> 6426 bytes quartz/static/og-image.png | Bin 39281 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 quartz/static/og-image.png diff --git a/content/index.md b/content/index.md index 6a6e7976..110a4eee 100644 --- a/content/index.md +++ b/content/index.md @@ -1,5 +1,5 @@ --- -title: Welcome! 👋 +title: Ellie's Notes date: 0202-09-01 --- diff --git a/quartz/static/icon.png b/quartz/static/icon.png index b6656a7a819cf41ba6502b9eddf4e580617bbaba..982afcf6b59d5ba116c25321929c4f8d43c5fc06 100644 GIT binary patch literal 6426 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 P00000NkvXXu0mjf;?EWX diff --git a/quartz/static/og-image.png b/quartz/static/og-image.png deleted file mode 100644 index f1321455b370d6ac9c89fa26b678a84972092c05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39281 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 From efc858540146bc0e4175e638a26d46c8466751e1 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Thu, 14 Sep 2023 09:09:09 +0100 Subject: [PATCH 09/94] Quartz sync: Sep 14, 2023, 9:09 AM --- content/index.md | 2 +- content/life/crashing motorcycles.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 content/life/crashing motorcycles.md diff --git a/content/index.md b/content/index.md index 110a4eee..c55f820a 100644 --- a/content/index.md +++ b/content/index.md @@ -1,6 +1,6 @@ --- title: Ellie's Notes -date: 0202-09-01 +date: 2023-09-01 ---
diff --git a/content/life/crashing motorcycles.md b/content/life/crashing motorcycles.md new file mode 100644 index 00000000..4d6e3deb --- /dev/null +++ b/content/life/crashing motorcycles.md @@ -0,0 +1,3 @@ +--- +draft: true +--- From cddc2507a3bfeaa8c181423f1d05cc15756a7a73 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Thu, 14 Sep 2023 09:09:30 +0100 Subject: [PATCH 10/94] Quartz sync: Sep 14, 2023, 9:09 AM --- content/life/crashing motorcycles.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 content/life/crashing motorcycles.md diff --git a/content/life/crashing motorcycles.md b/content/life/crashing motorcycles.md deleted file mode 100644 index 4d6e3deb..00000000 --- a/content/life/crashing motorcycles.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -draft: true ---- From 90ed276c89d1c5d300460634a60083fddacc3d56 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 16 Sep 2023 18:04:04 +0100 Subject: [PATCH 11/94] Quartz sync: Sep 16, 2023, 6:04 PM --- content/infra/alertmanager.md | 15 ------ content/infra/kubernetes.md | 41 --------------- content/infra/linux filesystems.md | 23 --------- content/infra/networking.md | 19 ------- content/infra/sqlite.md | 14 ----- content/links.md | 9 ---- .../exporting prometheus metrics with axum.md | 3 +- .../geospatial queries with mongodb.md | 3 +- .../{tools/git.md => notes/git snippets.md} | 7 ++- content/notes/helmfile.md | 26 ++++++++++ content/notes/index.md | 4 ++ content/{tools/jq.md => notes/jq snippets.md} | 5 +- content/notes/kube state metrics.md | 7 +++ content/notes/linux.md | 37 ++++++++++++++ .../postgres.md => notes/postgresql.md} | 9 ++-- content/{programming => notes}/python.md | 12 +++-- content/notes/resizing pvc for statefulset.md | 18 +++++++ .../rest apis and swiftui.md | 2 +- content/{programming => notes}/rust.md | 6 ++- .../scaling kube state metrics.md} | 13 ++--- ...d could not open shared memory segment.md} | 21 ++++---- .../notes/trigger alertmanager manually.md | 15 ++++++ content/programming/index.md | 6 --- content/tools/mdbook.md | 51 ------------------- quartz.layout.ts | 1 + quartz/components/ExplorerNode.tsx | 1 + quartz/styles/custom.scss | 4 +- 27 files changed, 149 insertions(+), 223 deletions(-) delete mode 100644 content/infra/alertmanager.md delete mode 100644 content/infra/kubernetes.md delete mode 100644 content/infra/linux filesystems.md delete mode 100644 content/infra/networking.md delete mode 100644 content/infra/sqlite.md delete mode 100644 content/links.md rename content/{programming => notes}/exporting prometheus metrics with axum.md (98%) rename content/{programming => notes}/geospatial queries with mongodb.md (96%) rename content/{tools/git.md => notes/git snippets.md} (89%) create mode 100644 content/notes/helmfile.md create mode 100644 content/notes/index.md rename content/{tools/jq.md => notes/jq snippets.md} (84%) create mode 100644 content/notes/kube state metrics.md create mode 100644 content/notes/linux.md rename content/{infra/postgres.md => notes/postgresql.md} (89%) rename content/{programming => notes}/python.md (89%) create mode 100644 content/notes/resizing pvc for statefulset.md rename content/{programming => notes}/rest apis and swiftui.md (97%) rename content/{programming => notes}/rust.md (84%) rename content/{infra/kube state metrics.md => notes/scaling kube state metrics.md} (78%) rename content/{infra/systemd.md => notes/systemd could not open shared memory segment.md} (66%) create mode 100644 content/notes/trigger alertmanager manually.md delete mode 100644 content/programming/index.md delete mode 100644 content/tools/mdbook.md diff --git a/content/infra/alertmanager.md b/content/infra/alertmanager.md deleted file mode 100644 index 39b79490..00000000 --- a/content/infra/alertmanager.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -permalink: infra/alertmanager -date: 2023-09-01 -title: Alertmanager ---- -# Alertmanager - -## Trigger alert manually -Useful to test that you've actually set it all up properly! - -``` -curl -H 'Content-Type: application/json' -d '[{"labels":{"alertname":"test-alert"}}]' http://alertmanager:9093/api/v1/alerts - --> {"status": "success"} -``` \ No newline at end of file diff --git a/content/infra/kubernetes.md b/content/infra/kubernetes.md deleted file mode 100644 index 14d1edb3..00000000 --- a/content/infra/kubernetes.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -permalink: infra/kubernetes -date: 2023-09-01 -title: Kubernetes ---- -# Kubernetes - -## Operations - -### Resizing a PVC for a StatefulSet - -If you've ever tried to resize a statefulset PVC for a database deployment (or similar), you have probably seen this error - -> Internal server error -> -> StatefulSet.apps "es-cluster" is invalid: spec: Forbidden: updates to statefulset > spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden. - -1. Edit all the PVCs manually: `kubectl edit pvc -n ` -2. Delete the StatefulSet, but leave the pods running: `kubectl delete -n statefulset --cascade=orphan ` -3. Recreate the StatefulSet with `helm upgrade`, `kubectl apply`, or however else you deploy stuff -4. Restart the StatefulSet to redeploy all the pods `kubectl rollout restart statefulset -n namespace ` - -## Helmfile - -Helm is totally adequate (I'm uncomfortable calling it good) for deploying an -app, and potentially a small number of dependencies. It can quickly spiral out -of control when there are many services that need deploying, with varying -levels of dependency. - -Helmfile is great for describing a large set of charts to deploy, and their -values. It's nice to be able to set values per environment, and to be able to -deploy charts from a variety of sources - git repos, official repos, and the -local filesystem. - -### write-values - -Ensuring you get the correct YAML and Go template encantation can be annoying, -and testing it out by deploying is not ideal. In lieu of proper testing, -`helmfile write-values` is great as it runs all the templating and dumps files -on disk for inspection - diff --git a/content/infra/linux filesystems.md b/content/infra/linux filesystems.md deleted file mode 100644 index 5370c14c..00000000 --- a/content/infra/linux filesystems.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -permalink: infra/filesystems -date: 2023-09-01 -title: Linux Filesystems ---- - -Just some Linux-y notes - -### Filesystem types -``` -fd00 - Linux RAID -``` - -### Creating software RAID device -``` -mdadm --create --verbose /dev/md --level= --raid-devices= /dev/one /dev/two ... -``` - -### Get device UUID - -``` -blkid -``` \ No newline at end of file diff --git a/content/infra/networking.md b/content/infra/networking.md deleted file mode 100644 index b6e7fb42..00000000 --- a/content/infra/networking.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -permalink: infra/networking -date: 2023-09-01 -title: Networking ---- -# Networking - -### Interfaces -- Interface names must be <16 chars - -### Create bridge -``` -ip link add br0 type bridge -``` - -### Add IP to bridge -``` -ip addr add 172.16.0.1/16 dev br0 -``` diff --git a/content/infra/sqlite.md b/content/infra/sqlite.md deleted file mode 100644 index 3e656191..00000000 --- a/content/infra/sqlite.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -permalink: infra/sqlite -date: 2023-09-01 -title: SQLite ---- -# SQLite - -SQLite is pretty up there as one of my favourite pieces of software! It's reliable, performant, and has never let me down. - -I use it for [atuin](atuin.md). - -## Thoughts -- Enable the WAL. It's fantastic, boosting performance and durability -- The FTS modules are actually surprisingly effective, and very fast diff --git a/content/links.md b/content/links.md deleted file mode 100644 index 1fbea1c3..00000000 --- a/content/links.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -permalink: links -date: 2023-09-01 ---- -- [time2reach](https://map.henryn.xyz/) Shows the time to travel from a given point. Useful for house hunting, known as an [Isochrone map](https://en.wikipedia.org/wiki/Isochrone_map) -- [pgcat](https://github.com/postgresml/pgcat), a postgres proxy #database #rust -- [supavisor](https://supabase.com/blog/supavisor-1-million) , more postgres proxy-ing #database #elixir -- [pg_partman](https://github.com/pgpartman/pg_partman/), postgres partition management extension #database -- [pueue](https://github.com/Nukesor/pueue), a shell process manager #shell \ No newline at end of file diff --git a/content/programming/exporting prometheus metrics with axum.md b/content/notes/exporting prometheus metrics with axum.md similarity index 98% rename from content/programming/exporting prometheus metrics with axum.md rename to content/notes/exporting prometheus metrics with axum.md index 7ab3d47f..fd0a1c1d 100644 --- a/content/programming/exporting prometheus metrics with axum.md +++ b/content/notes/exporting prometheus metrics with axum.md @@ -1,9 +1,8 @@ --- -permalink: programming/exporting-prometheus-metrics-with-axum +permalink: notes/exporting-prometheus-metrics-with-axum draft: false tags: - rust - - til date: 2023-09-13 title: Exporting Prometheus metrics with Axum --- diff --git a/content/programming/geospatial queries with mongodb.md b/content/notes/geospatial queries with mongodb.md similarity index 96% rename from content/programming/geospatial queries with mongodb.md rename to content/notes/geospatial queries with mongodb.md index 7acf105d..a2e28e5c 100644 --- a/content/programming/geospatial queries with mongodb.md +++ b/content/notes/geospatial queries with mongodb.md @@ -1,10 +1,9 @@ --- title: Geospatial queries with MongoDB date: 2019-07-25 -permalink: programming/geospatial-queries-with-mongodb +permalink: notes/geospatial-queries-with-mongodb tags: - python - - til --- I'm currently playing with MongoDB and its [geospatial queries](https://docs.mongodb.com/manual/geospatial-queries/?ref=ellie.wtf). It’s pretty interesting so far, so I just thought I'd write something up to show how I'm using it with PyMongo! diff --git a/content/tools/git.md b/content/notes/git snippets.md similarity index 89% rename from content/tools/git.md rename to content/notes/git snippets.md index 0fc7aec0..0f2222ee 100644 --- a/content/tools/git.md +++ b/content/notes/git snippets.md @@ -1,11 +1,10 @@ --- -permalink: tools/git +permalink: notes/git-snippets date: 2023-09-01 tags: - - tool + - tools +title: Git snippets --- -# Git - ## Remove untracked files ``` git clean -fdx diff --git a/content/notes/helmfile.md b/content/notes/helmfile.md new file mode 100644 index 00000000..a60124a4 --- /dev/null +++ b/content/notes/helmfile.md @@ -0,0 +1,26 @@ +--- +permalink: notes/helmfile +date: 2023-09-01 +tags: + - infra + - kubernetes +title: Helmfile +--- +## Helmfile + +Helm is totally adequate (I'm uncomfortable calling it good) for deploying an +app, and potentially a small number of dependencies. It can quickly spiral out +of control when there are many services that need deploying, with varying +levels of dependency. + +Helmfile is great for describing a large set of charts to deploy, and their +values. It's nice to be able to set values per environment, and to be able to +deploy charts from a variety of sources - git repos, official repos, and the +local filesystem. + +### write-values + +Ensuring you get the correct YAML and Go template encantation can be annoying, +and testing it out by deploying is not ideal. In lieu of proper testing, +`helmfile write-values` is great as it runs all the templating and dumps files +on disk for inspection diff --git a/content/notes/index.md b/content/notes/index.md new file mode 100644 index 00000000..6a2c694a --- /dev/null +++ b/content/notes/index.md @@ -0,0 +1,4 @@ +--- +title: Notes +--- +A collection of my notes. These are better browsed by tag or by links, as otherwise all topics will be mixed. \ No newline at end of file diff --git a/content/tools/jq.md b/content/notes/jq snippets.md similarity index 84% rename from content/tools/jq.md rename to content/notes/jq snippets.md index bc4e78f4..f95829ed 100644 --- a/content/tools/jq.md +++ b/content/notes/jq snippets.md @@ -1,8 +1,7 @@ --- -permalink: tools/jq +permalink: notes/jq-snippets date: 2023-09-01 -tags: - - tool +title: jq snippets --- # jq diff --git a/content/notes/kube state metrics.md b/content/notes/kube state metrics.md new file mode 100644 index 00000000..1b95b4c6 --- /dev/null +++ b/content/notes/kube state metrics.md @@ -0,0 +1,7 @@ +--- +permalink: notes/kube-state-metrics +date: 2023-09-01 +title: Kube State Metrics +--- + +Kube state metrics exposes kubernetes metrics in a way that they can be scraped by prometheus. It uses the kubernetes API to create a snapshot of the state of your cluster, and then exposes that on `/metrics`, ready to be ingested by your monitoring. \ No newline at end of file diff --git a/content/notes/linux.md b/content/notes/linux.md new file mode 100644 index 00000000..6de9a336 --- /dev/null +++ b/content/notes/linux.md @@ -0,0 +1,37 @@ +--- +permalink: notes/linux +date: 2023-09-01 +title: Linux +--- + +Just a reference page for all the linux things I forget +### Filesystem types +``` +fd00 - Linux RAID +``` + +### Creating software RAID device +``` +mdadm --create --verbose /dev/md --level= --raid-devices= /dev/one /dev/two ... +``` + +### Get device UUID + +``` +blkid +``` + +## Networking + +### Interfaces +- Interface names must be <16 chars + +### Create bridge +``` +ip link add br0 type bridge +``` + +### Add IP to bridge +``` +ip addr add 172.16.0.1/16 dev br0 +``` \ No newline at end of file diff --git a/content/infra/postgres.md b/content/notes/postgresql.md similarity index 89% rename from content/infra/postgres.md rename to content/notes/postgresql.md index 3c78d09e..34baf23e 100644 --- a/content/infra/postgres.md +++ b/content/notes/postgresql.md @@ -1,9 +1,10 @@ --- -permalink: infra/postgres +permalink: notes/postgresql date: 2023-09-01 -title: Postgres +title: PostgreSQL --- -# PostgreSQL + +Some postgres snippets! Just a reference page for the things I forget a lot. - `pg_ctl init -D path` - init new database + config at `path` - `pg_hba.conf` - configure host based auth @@ -61,4 +62,4 @@ SELECT extract(epoch from now() - pg_last_xact_replay_timestamp()) AS replica_la ## Useful tools -- [WAL streaming and backup via object storage](https://github.com/wal-g/wal-g) +- [WAL streaming and backup via object storage](https://github.com/wal-g/wal-g) \ No newline at end of file diff --git a/content/programming/python.md b/content/notes/python.md similarity index 89% rename from content/programming/python.md rename to content/notes/python.md index 2cced3af..08a5b994 100644 --- a/content/programming/python.md +++ b/content/notes/python.md @@ -1,11 +1,12 @@ --- -permalink: programming/python +permalink: notes/python +date: 2023-09-01 +title: Python tags: - python - - evergreen -title: Python -date: 2023-09-01 --- +My Python reference page + I don't spend as much time building actual large projects in Python any more (though I was paid to write Python for a few years in my early career). These days it's mostly just for random glue scripts on a variety of systems. ### Quick s3 client @@ -30,4 +31,5 @@ def chunks(lst, n): yield lst[i:i + n] ``` -From [StackOverflow](https://stackoverflow.com/questions/312443/how-do-i-split-a-list-into-equally-sized-chunks) \ No newline at end of file +From [StackOverflow](https://stackoverflow.com/questions/312443/how-do-i-split-a-list-into-equally-sized-chunks) + diff --git a/content/notes/resizing pvc for statefulset.md b/content/notes/resizing pvc for statefulset.md new file mode 100644 index 00000000..73d10650 --- /dev/null +++ b/content/notes/resizing pvc for statefulset.md @@ -0,0 +1,18 @@ +--- +permalink: notes/resizing-pvc-for-statefulset +date: 2023-09-01 +tags: + - infra + - kubernetes +title: Resizing PVC for a Kubernetes Statefulset +--- +If you've ever tried to resize a kubernetes statefulset PVC for a database deployment (or similar), you have probably seen this error + +> Internal server error +> +> StatefulSet.apps "es-cluster" is invalid: spec: Forbidden: updates to statefulset > spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden. + +1. Edit all the PVCs manually: `kubectl edit pvc -n ` +2. Delete the StatefulSet, but leave the pods running: `kubectl delete -n statefulset --cascade=orphan ` +3. Recreate the StatefulSet with `helm upgrade`, `kubectl apply`, or however else you deploy stuff +4. Restart the StatefulSet to redeploy all the pods `kubectl rollout restart statefulset -n namespace ` diff --git a/content/programming/rest apis and swiftui.md b/content/notes/rest apis and swiftui.md similarity index 97% rename from content/programming/rest apis and swiftui.md rename to content/notes/rest apis and swiftui.md index dfbacb03..9bc8795a 100644 --- a/content/programming/rest apis and swiftui.md +++ b/content/notes/rest apis and swiftui.md @@ -1,6 +1,6 @@ --- date: 2020-02-23 -permalink: programming/rest-apis-and-swiftui +permalink: notes/rest-apis-and-swiftui tags: - swift title: Rest APIs and SwiftUI diff --git a/content/programming/rust.md b/content/notes/rust.md similarity index 84% rename from content/programming/rust.md rename to content/notes/rust.md index 87ada1a5..4bd88a61 100644 --- a/content/programming/rust.md +++ b/content/notes/rust.md @@ -1,11 +1,12 @@ --- -permalink: programming/rust +permalink: notes/rust tags: - rust - evergreen title: Rust date: 2023-09-01 --- +My Rust reference page ## Projects - [[atuin | Atuin]] ## Cargo @@ -41,3 +42,6 @@ Usage: ```rust const SHA: &str = env!("GIT_HASH"); ``` + +# links +- [a leaky bucket rate limiter](https://github.com/udoprog/leaky-bucket) diff --git a/content/infra/kube state metrics.md b/content/notes/scaling kube state metrics.md similarity index 78% rename from content/infra/kube state metrics.md rename to content/notes/scaling kube state metrics.md index d096aecd..c4b647ba 100644 --- a/content/infra/kube state metrics.md +++ b/content/notes/scaling kube state metrics.md @@ -1,15 +1,8 @@ --- -permalink: infra/kube-state-metrics -date: 2023-09-01 -title: Kube State Metrics +permalink: notes/scaling-kube-state-metrics +title: Scaling Kube State Metrics --- -# Kube State Metrics - -Kube state metrics exposes kubernetes metrics in a way that they can be scraped by prometheus. It uses the kubernetes API to create a snapshot of the state of your cluster, and then exposes that on `/metrics`, ready to be ingested by your monitoring. - -### Scaling - -With larger clusters, you might find that scrapes begin to take time - ksm might be exposing millions of samples, depending on how many nodes/pods/etc you have! +With larger clusters, you might find that scrapes begin to take time - [[notes/kube state metrics|kube state metrics]] might be exposing millions of samples, depending on how many nodes/pods/etc you have! KSM supports sharding, both [automatically](https://github.com/kubernetes/kube-state-metrics#automated-sharding) and manually. Unfortunately the autosharding is still very much in development diff --git a/content/infra/systemd.md b/content/notes/systemd could not open shared memory segment.md similarity index 66% rename from content/infra/systemd.md rename to content/notes/systemd could not open shared memory segment.md index 0723ccfa..f5ff651b 100644 --- a/content/infra/systemd.md +++ b/content/notes/systemd could not open shared memory segment.md @@ -1,20 +1,19 @@ --- -permalink: infra/systemd +permalink: notes/systemd-could-not-open-shared-memory-segment date: 2023-09-01 -title: Systemd +tags: + - systemd + - linux + - postgresql +title: Systemd could not open shared memory segment --- +I spent a while operating [[postgresql | PostgreSQL]] running on an EC2 instance. We had a weird problem where sometimes queries would fail, with the error -# Systemd +> `could not open shared memory segment "/PostgreSQL.271741757"`. -Just some issues I have had with it +For non-system (UID < 1000) users, `logind` will by default clear shared mem files upon logout. For things like databases, this is not at all desireable. One thing to note is that it's unlikely for this to happen if you never login as the database user. -## /dev/shm clearing - -This can also be seen by logs such as `could not open shared memory segment "/PostgreSQL.271741757"`. - -For non-system (UID < 1000) users, `logind` will by default clear shared mem files upon logout. For things like databases, this is not at all desireable. - -Setting `RemoveIPC=no` in `/etc/systemd/logind.conf` should resolve this. +Setting `RemoveIPC=no` in `/etc/systemd/logind.conf` should resolve this. In theory, you can restart logind while the system is running. I did some tests, but I think it's still probably safest to reboot the system. Man page diff --git a/content/notes/trigger alertmanager manually.md b/content/notes/trigger alertmanager manually.md new file mode 100644 index 00000000..b8376b3f --- /dev/null +++ b/content/notes/trigger alertmanager manually.md @@ -0,0 +1,15 @@ +--- +permalink: notes/trigger-alertmanager-manually +title: Trigger Alertmanager Manually +date: 2023-09-01 +--- +I was setting up Alertmanager and needed to manually trigger an alert to make sure it works + +Something like this will do it, depending on your port: + +``` +curl -H 'Content-Type: application/json' -d '[{"labels":{"alertname":"test-alert"}}]' http://alertmanager:9093/api/v1/alerts + +-> {"status": "success"} +``` + diff --git a/content/programming/index.md b/content/programming/index.md deleted file mode 100644 index bb5f8919..00000000 --- a/content/programming/index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Programming -date: 2023-09-01 ---- - -A collection of programming notes. Some are regular-updated snippets (labeled evergreen), while others are more of a one-off (labeled til) \ No newline at end of file diff --git a/content/tools/mdbook.md b/content/tools/mdbook.md deleted file mode 100644 index 112f9946..00000000 --- a/content/tools/mdbook.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -permalink: tools/mdbook -date: 2023-09-01 -tags: - - tool ---- -# mdbook - -I was trying out mdbook for this, but no longer use it. I'll keep the notes just in case. - -## Vercel deployments -I currently deploy static sites to vercel, because I'm lazy and don't want to spend _even more time_ doing infra. - -To get mdbook to deploy successfully: - -1. Set Framework Preset to "Other" -2. Set Output Directory to "book" -3. Set Build Command to: -``` -curl -Lo mdbook.tar.gz https://github.com/rust-lang/mdBook/releases/download/v0.4.32/mdbook-v0.4.32-x86_64-unknown-linux-musl.tar.gz; tar -xvzf mdbook.tar.gz; ./mdbook build -``` - -You'll probably want to update the version there. Downloading from the release page, we can deploy a wiki in 4s! - -Alternatively, you could build from source - though this will take minutes, not seconds: -``` -amazon-linux-extras install rust1; cargo install mdbook; mdbook build -``` - -The only upside I can see here is that it's automatically the newest version - -## Analytics -I use [Plausible](https://plausible.io). mdbook supports custom javascript, but only with a script file in your repo. Something like this does nicely: - -book.toml -```toml -[output.html] -additional-js = ["plausible.js"] -``` - -plausible.js -```javascript -const script = document.createElement('script'); - -script.src = "https://plausible.io/js/script.js"; - -script.defer = true; -script.setAttribute("data-domain", "YOUR DOMAIN HERE"); // you probs want to change this - -document.body.appendChild(script); -``` \ No newline at end of file diff --git a/quartz.layout.ts b/quartz.layout.ts index 42b25709..f07a693f 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -22,6 +22,7 @@ export const defaultContentPageLayout: PageLayout = { Component.MobileOnly(Component.Spacer()), Component.Search(), Component.Darkmode(), + Component.DesktopOnly(Component.Explorer()), Component.DesktopOnly(Component.RecentNotes()), ], right: [ diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index 6718ec9f..8e7e9913 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -7,6 +7,7 @@ export interface Options { folderDefaultState: "collapsed" | "open" folderClickBehavior: "collapse" | "link" useSavedState: boolean + hidden?: [string] } type DataWrapper = { diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss index 020d48b8..b06d3a33 100644 --- a/quartz/styles/custom.scss +++ b/quartz/styles/custom.scss @@ -1,6 +1,6 @@ // put your custom CSS here! -p, li { - font-size: 1.2rem; +article li, p { + font-size: 1.2rem !important; line-height: 1.5 !important; } From bbcf9d728c89fdd4577b8ab37322d3361008c18c Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 16 Sep 2023 18:04:37 +0100 Subject: [PATCH 12/94] Quartz sync: Sep 16, 2023, 6:04 PM --- content/notes/scaling kube state metrics.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/notes/scaling kube state metrics.md b/content/notes/scaling kube state metrics.md index c4b647ba..39ac546c 100644 --- a/content/notes/scaling kube state metrics.md +++ b/content/notes/scaling kube state metrics.md @@ -1,6 +1,7 @@ --- permalink: notes/scaling-kube-state-metrics title: Scaling Kube State Metrics +date: 2023-09-01 --- With larger clusters, you might find that scrapes begin to take time - [[notes/kube state metrics|kube state metrics]] might be exposing millions of samples, depending on how many nodes/pods/etc you have! From d7da8c028b9d53270e7b42e966c1cb46cac22c2a Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 16 Sep 2023 18:11:37 +0100 Subject: [PATCH 13/94] Quartz sync: Sep 16, 2023, 6:11 PM --- content/notes/scaling kube state metrics.md | 3 +++ quartz/styles/custom.scss | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/content/notes/scaling kube state metrics.md b/content/notes/scaling kube state metrics.md index 39ac546c..3e2c0c6a 100644 --- a/content/notes/scaling kube state metrics.md +++ b/content/notes/scaling kube state metrics.md @@ -2,6 +2,9 @@ permalink: notes/scaling-kube-state-metrics title: Scaling Kube State Metrics date: 2023-09-01 +tags: + - kubernetes + - infra --- With larger clusters, you might find that scrapes begin to take time - [[notes/kube state metrics|kube state metrics]] might be exposing millions of samples, depending on how many nodes/pods/etc you have! diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss index b06d3a33..8ef24222 100644 --- a/quartz/styles/custom.scss +++ b/quartz/styles/custom.scss @@ -1,6 +1,6 @@ // put your custom CSS here! -article li, p { - font-size: 1.2rem !important; +article p, article li{ + font-size: 1.1rem !important; line-height: 1.5 !important; } From 06817cbbd79a56327b7b44c2f9d94840351212b4 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 16 Sep 2023 18:37:18 +0100 Subject: [PATCH 14/94] Quartz sync: Sep 16, 2023, 6:37 PM --- content/index.md | 2 +- content/notes/postgresql.md | 3 ++ content/notes/rest apis and swiftui.md | 2 +- content/places/san francisco.md | 11 +++++ content/posts/rest apis and swiftui.md | 57 -------------------------- 5 files changed, 16 insertions(+), 59 deletions(-) create mode 100644 content/places/san francisco.md delete mode 100644 content/posts/rest apis and swiftui.md diff --git a/content/index.md b/content/index.md index c55f820a..4e5e3479 100644 --- a/content/index.md +++ b/content/index.md @@ -22,7 +22,7 @@ This site is constantly shifting, but here are some things you may be interested - [Posts](/posts), a selection of my longer-form writing and thoughts - [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod | iPod]] and [[atuin | Atuin]]. - [Life](/life), where I'm writing about my travels, adventures, and life in general -- I also keep some notes on [programming](/programming), [infra](/infra), and [tools](/tools). Have an explore! +- I also keep some [notes](/notes) on a variety of topics! It may be easier to browse those by tag. ## Speaking I gave my first talk at [FOSDEM in 2023](https://www.youtube.com/watch?v=uyRmV19qJ2o), and am looking forward to giving more in the future! diff --git a/content/notes/postgresql.md b/content/notes/postgresql.md index 34baf23e..78f2d28b 100644 --- a/content/notes/postgresql.md +++ b/content/notes/postgresql.md @@ -2,6 +2,9 @@ permalink: notes/postgresql date: 2023-09-01 title: PostgreSQL +tags: + - postgresql + - infra --- Some postgres snippets! Just a reference page for the things I forget a lot. diff --git a/content/notes/rest apis and swiftui.md b/content/notes/rest apis and swiftui.md index 9bc8795a..8f950d4e 100644 --- a/content/notes/rest apis and swiftui.md +++ b/content/notes/rest apis and swiftui.md @@ -33,7 +33,7 @@ Actually no. There's something called an [ObservableObject](https://developer.ap An example will probably illustrate this more effectively -``` +```swift struct User: ObservableObject { @Published var username: String @Published var name: String diff --git a/content/places/san francisco.md b/content/places/san francisco.md new file mode 100644 index 00000000..c72c9d38 --- /dev/null +++ b/content/places/san francisco.md @@ -0,0 +1,11 @@ +--- +locations: +--- + +A collection of places I like in and around SF + +## Motorcycles +- [Alice's Restaurant](geo:37.38668665,-122.26539841154792) tag:motorcycle + +## Sunsets +- \ No newline at end of file diff --git a/content/posts/rest apis and swiftui.md b/content/posts/rest apis and swiftui.md deleted file mode 100644 index ab64bd06..00000000 --- a/content/posts/rest apis and swiftui.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -date: 2020-02-23 -permalink: posts/rest-apis-and-swiftui -tags: - - swift -title: Rest APIs and SwiftUI ---- - -I recently had someone ask me how I handled fetching and rendering data from a REST API in a SwiftUI view. - -If you're coming from something like React, it's actually handled differently. We can't just fire off a request in `init` or `onAppear`, and update some `@State`. Awkwardness with `escaping` closures, `struct`s not being mutable, etc etc etc. - -Luckily SwiftUI has some constructs that can help :) I won't be going over how to make GET requests or whatever in detail, though I like to use [Just](https://github.com/dduan/Just?ref=ellie.wtf). - -Firsts up, let's have a view - -```swift -struct UserProfile: View { - @ObservedObject user: User = User(username: "elm", name: "Ellie") - - var body: some View { - VStack { - Text("Username: \(self.user.username)") - Text("Name: \(self.user.name)") - } - } -} -``` - -So, how do we get the `user` object here loaded? `@State`? - -Actually no. There's something called an [ObservableObject](https://developer.apple.com/documentation/combine/observableobject?ref=ellie.wtf). This is an object with `@Published` properties. If a `@Published` property is updated, any view containing this object will have an update triggered. - -An example will probably illustrate this more effectively - -``` -struct User: ObservableObject { - @Published var username: String - @Published var name: String - - init(username: String, name: String) { - self.username = username - self.name = name - - self.load() - } - - func load() { - somehowRequestDataAsynchronously(); - // so basically just change `username` or `name` in here - } -} -``` - -The view essentially "observes" something "observable" - `@ObservedObject` and `@ObservableObject`. As soon as a `@Published` property is updated on something we are observing, a re-render is triggered and the view updates! - -Hopefully this clears things up! \ No newline at end of file From e6d50c59a9fdb12fa81278867e6b4aed746e94e7 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 16 Sep 2023 18:38:43 +0100 Subject: [PATCH 15/94] Quartz sync: Sep 16, 2023, 6:38 PM --- content/places/san francisco.md | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 content/places/san francisco.md diff --git a/content/places/san francisco.md b/content/places/san francisco.md deleted file mode 100644 index c72c9d38..00000000 --- a/content/places/san francisco.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -locations: ---- - -A collection of places I like in and around SF - -## Motorcycles -- [Alice's Restaurant](geo:37.38668665,-122.26539841154792) tag:motorcycle - -## Sunsets -- \ No newline at end of file From b3dcabd9d689e5f7cca70da5db83a18ceb344e28 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 25 Sep 2023 20:38:53 -0700 Subject: [PATCH 16/94] Quartz sync: Sep 25, 2023, 8:38 PM --- content/life/2022.md | 1 + content/life/san francisco to fort bragg.md | 57 +++++++++++++++++++ content/podcasts.md | 2 + content/projects/atuin.md | 4 +- .../projects/yeet a personal image host.md | 3 +- 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 content/life/san francisco to fort bragg.md create mode 100644 content/podcasts.md diff --git a/content/life/2022.md b/content/life/2022.md index cc666953..9668a465 100644 --- a/content/life/2022.md +++ b/content/life/2022.md @@ -3,6 +3,7 @@ date: 2023-01-10 cover: https://img.ellie.wtf/i/2e8befc770a7ab4cf77280710d3851a79217c5512bd4adb1faf4ef279306d26c.jpg description: 2022 has probably been one of the best years of my life, so I'd like to do my best to document everything that has happened - away from social media, and on something a little longer form! title: 2022, my year in review +tags: [usa] --- ![](https://img.ellie.wtf/i/2e8befc770a7ab4cf77280710d3851a79217c5512bd4adb1faf4ef279306d26c.jpg) diff --git a/content/life/san francisco to fort bragg.md b/content/life/san francisco to fort bragg.md new file mode 100644 index 00000000..777e5cc1 --- /dev/null +++ b/content/life/san francisco to fort bragg.md @@ -0,0 +1,57 @@ +--- +title: San Francisco to Fort Bragg +date: 2023-09-25 +tags: + - usa + - wc23 +cover: https://img.ellie.wtf/i/44ef0a6f44d420ec5f6a206fa5b13fbe0f8cf78a0b0a299b3a5793b3ff7cb5f6.jpg +permalink: life/sf-to-ft-bragg +--- +![](https://img.ellie.wtf/i/44ef0a6f44d420ec5f6a206fa5b13fbe0f8cf78a0b0a299b3a5793b3ff7cb5f6.jpg) + +I've been in California for a few days now, staying with a friend in San Francisco. Today we left for the first day of a 6-day motorcycle trip! I've decided I'm going to write more on my own website, whereas I used to post much more about my travels to instagram. + +Today we did 257 miles, and rode for just under 6 hours. + +> [!warning] +> This post is pretty image heavy, and some of them are not optimised. + +![](https://img.ellie.wtf/i/fc70886f296b79b55304055ec429e734a34bf99931cc4cc5115015b24bde18a9.jpeg) + +We started at Spike's Coffee and Tea, in the Castro. From there, we rode over the Golden Gate Bridge and up Highway One. We could have stuck with hw 1 the entire route, but to keep things interesting (and take the twistier route), we had several detours! + +The first food stop was at Route One Bakery. It was pretty quiet (unsurprising for a Monday morning), but apparently at weekends is pretty busy with lots of bikers. + +![](https://img.ellie.wtf/i/51c4121d8b13cd29ff1df81dd6b8e02db2d3772534a801bd4f5dfd8dd869e184.jpeg) + +I made friends with a chicken! She enjoyed the crumbs from my cookie + +![](https://img.ellie.wtf/i/15b24b448f11e2dd473f3723abff3d721b1ca968cf61d7e01de6c15952b13197.jpeg) + +After this stop, we rode for a while longer, on a mix of hw 1 and some twistier side roads. I really like how this part of the world is a big mix of mountains, forests, and coast + +The last time I had ridden this route was in [[2022]], and I stopped at a cafe (Cafe Aquatica) before turning back. Seeing as this time we were going to be going _much_ further north, I thought it would make a nice tea break. + +I'd been wanting to stop to take a photo, so after a period of riding uphill we found a good opportunity for a photo! It was so incredibly quiet that stopping in the middle of the road wasn't a problem. + +![](https://img.ellie.wtf/i/f96d509bcf67d608ae0a1ca815cfb52f5c18fb7e65a9fedd1ddfae547ca1177e.jpg) + +This road was probably the "wildest" part yet, and went through a lot of forest! Eventually it rejoined hw 1, which we stayed on for a while before heading off onto Skaggs Springs Road. I'm naming this one specifically, as it was definitely my favourite part of the day! There was a really nice bridge that made another nice photo spot, and afterwards the corners were very smooth and sweeping. + +After crashing in the UK a few weeks ago, the past 2 days have been the first time I've been back on a bike. Having such a nice stretch of road really helped me feel more confident riding again + +![](https://img.ellie.wtf/i/91936c78f2958557f06934e212c0cfd3e192026d9c8cb695900fe25c52444778.jpg) + +After Skaggs, we weren't too far from our stop for the day! The majority of the day had been absolutely amazing, so of course we were due some minor issues. + +We had about 1hr 30 left when I felt a sting of pain on my neck. This isn't unusual, sometimes small rocks or other small debris get flicked up from the road. What was unusual was that the stinging didn't stop. We pulled over, and it turns out I'd been stung by a wasp! It had found its way inside my jacket, and luckily didn't keep stinging. In all the years I've been riding, this has never happened - but has been a bit of a fear. + +Rainy showers had been forecast pretty much all day, and so far we had dodged them. A few raindrops were starting to appear on my visor towards the end, but it didn't seem like it would be bad. It started to get really heavy, though we were riding through a redwood forest and were pretty well sheltered. The forests all smelled absolutely **amazing**, which is a sense you totally miss out on while travelling in a car. + +The last hour or so was all rain, but I wasn't too bothered about it. Today I rode some of the best roads in a long time, and can't wait to see what tomorrow brings! + +![](https://img.ellie.wtf/i/ac2a9b30383f66afa09cfd561c06a9e2554f1eb043568c719412640d1c986363.jpeg) + +![](https://img.ellie.wtf/i/73f146707109be7b378e5a337a30404ff864ee7c3bca7913553ba11d754d07ea.jpeg) + +Thank you to Michelle for her fantastic hospitality while I've been here, letting me borrow her bike, and for taking me on this trip 💖 \ No newline at end of file diff --git a/content/podcasts.md b/content/podcasts.md new file mode 100644 index 00000000..fb1dd17b --- /dev/null +++ b/content/podcasts.md @@ -0,0 +1,2 @@ +A list of podcasts where the hosts are not exclusively men + diff --git a/content/projects/atuin.md b/content/projects/atuin.md index f1292680..6aa1e293 100644 --- a/content/projects/atuin.md +++ b/content/projects/atuin.md @@ -4,6 +4,8 @@ title: Atuin, magical shell history tags: - rust - tool + - sqlite + - postgresql date: 2021-05-10 --- @@ -13,4 +15,4 @@ Atuin is my longest-running project and certainly the most successful (depending For years I have thought that shell history could be improved. I was often working across multiple machines, and searching for commands I had ran months prior. Why couldn't I have all the history I'd ever ran, on all machines, at all times? -Atuin was written to try and solve that problem. I originally named it "shync", from shell and sync, but luckily managed to choose something better. +Atuin was written to try and solve that problem. I originally named it "shync", from shell and sync, but luckily managed to choose something better. \ No newline at end of file diff --git a/content/projects/yeet a personal image host.md b/content/projects/yeet a personal image host.md index 445a92ee..3b7943e9 100644 --- a/content/projects/yeet a personal image host.md +++ b/content/projects/yeet a personal image host.md @@ -3,4 +3,5 @@ permalink: projects/yeet draft: true --- - Written in [[rust | Rust]] -- TLS failed when the S3 bucket had a `.` in the name. I forgot that this breaks SSL certificates! \ No newline at end of file +- TLS failed when the S3 bucket had a `.` in the name. I forgot that this breaks SSL certificates! +- couldn't find a nice library for reading _and_ writing exif metadata. Currently requires gexiv2 \ No newline at end of file From 52dbcc3ff652235d575bfb78aeca98f01281a704 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 25 Sep 2023 20:46:38 -0700 Subject: [PATCH 17/94] Quartz sync: Sep 25, 2023, 8:46 PM --- content/life/san francisco to fort bragg.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/life/san francisco to fort bragg.md b/content/life/san francisco to fort bragg.md index 777e5cc1..668c2b88 100644 --- a/content/life/san francisco to fort bragg.md +++ b/content/life/san francisco to fort bragg.md @@ -4,10 +4,10 @@ date: 2023-09-25 tags: - usa - wc23 -cover: https://img.ellie.wtf/i/44ef0a6f44d420ec5f6a206fa5b13fbe0f8cf78a0b0a299b3a5793b3ff7cb5f6.jpg +cover: https://img.ellie.wtf/i/4bcd4bd7f48f92b49ea0008f192aa8dd23921f2913e4694cb9a8a74e002841f3.jpg permalink: life/sf-to-ft-bragg --- -![](https://img.ellie.wtf/i/44ef0a6f44d420ec5f6a206fa5b13fbe0f8cf78a0b0a299b3a5793b3ff7cb5f6.jpg) +![](https://img.ellie.wtf/i/4bcd4bd7f48f92b49ea0008f192aa8dd23921f2913e4694cb9a8a74e002841f3.jpg) I've been in California for a few days now, staying with a friend in San Francisco. Today we left for the first day of a 6-day motorcycle trip! I've decided I'm going to write more on my own website, whereas I used to post much more about my travels to instagram. @@ -34,13 +34,13 @@ The last time I had ridden this route was in [[2022]], and I stopped at a cafe ( I'd been wanting to stop to take a photo, so after a period of riding uphill we found a good opportunity for a photo! It was so incredibly quiet that stopping in the middle of the road wasn't a problem. -![](https://img.ellie.wtf/i/f96d509bcf67d608ae0a1ca815cfb52f5c18fb7e65a9fedd1ddfae547ca1177e.jpg) +![](https://img.ellie.wtf/i/bcf4a32d0f838e553340200a167b691f90c7abdc388ec25f1748e5cfacd69a7b.jpg) This road was probably the "wildest" part yet, and went through a lot of forest! Eventually it rejoined hw 1, which we stayed on for a while before heading off onto Skaggs Springs Road. I'm naming this one specifically, as it was definitely my favourite part of the day! There was a really nice bridge that made another nice photo spot, and afterwards the corners were very smooth and sweeping. After crashing in the UK a few weeks ago, the past 2 days have been the first time I've been back on a bike. Having such a nice stretch of road really helped me feel more confident riding again -![](https://img.ellie.wtf/i/91936c78f2958557f06934e212c0cfd3e192026d9c8cb695900fe25c52444778.jpg) +![](https://img.ellie.wtf/i/b3b627fb48719681938985af3c8189ff16af6032b62baebf93eaef21d1c2c421.jpg) After Skaggs, we weren't too far from our stop for the day! The majority of the day had been absolutely amazing, so of course we were due some minor issues. From 5439037b9788450785f1bab35f7177b66eae0a06 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 25 Sep 2023 20:47:01 -0700 Subject: [PATCH 18/94] Quartz sync: Sep 25, 2023, 8:47 PM --- content/podcasts.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 content/podcasts.md diff --git a/content/podcasts.md b/content/podcasts.md deleted file mode 100644 index fb1dd17b..00000000 --- a/content/podcasts.md +++ /dev/null @@ -1,2 +0,0 @@ -A list of podcasts where the hosts are not exclusively men - From 7e79af006ac8ab649014f44e4556d78f27817b02 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 25 Sep 2023 20:50:48 -0700 Subject: [PATCH 19/94] Quartz sync: Sep 25, 2023, 8:50 PM --- content/life/san francisco to fort bragg.md | 2 +- quartz/components/Head.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/life/san francisco to fort bragg.md b/content/life/san francisco to fort bragg.md index 668c2b88..fa036288 100644 --- a/content/life/san francisco to fort bragg.md +++ b/content/life/san francisco to fort bragg.md @@ -54,4 +54,4 @@ The last hour or so was all rain, but I wasn't too bothered about it. Today I ro ![](https://img.ellie.wtf/i/73f146707109be7b378e5a337a30404ff864ee7c3bca7913553ba11d754d07ea.jpeg) -Thank you to Michelle for her fantastic hospitality while I've been here, letting me borrow her bike, and for taking me on this trip 💖 \ No newline at end of file +Thank you to Michelle for her fantastic hospitality while I've been here, letting me borrow her bike, and for taking me on this trip \ No newline at end of file diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index db6976ea..42165be9 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -13,7 +13,7 @@ export default (() => { const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!) const iconPath = joinSegments(baseDir, "static/icon.png") - const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png` + const ogImagePath = fileData.frontmatter?.cover ?? `https://${cfg.baseUrl}/static/og-image.png` return ( From ad741406f152210c0d9f81aef694c1555031c473 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 26 Sep 2023 20:45:56 -0700 Subject: [PATCH 20/94] Quartz sync: Sep 26, 2023, 8:45 PM --- content/life/ft bragg to weaverville.md | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 content/life/ft bragg to weaverville.md diff --git a/content/life/ft bragg to weaverville.md b/content/life/ft bragg to weaverville.md new file mode 100644 index 00000000..a29aef95 --- /dev/null +++ b/content/life/ft bragg to weaverville.md @@ -0,0 +1,67 @@ +--- +title: Fort Bragg to Weaverville +tags: + - usa + - wc23 +cover: https://img.ellie.wtf/i/2ea431cfbcbd088aab0494a04535e864365237d71a3790005ae8c69738b6a8d5.jpg +date: 2023-09-26 +--- +![](https://img.ellie.wtf/i/e5e5abad2aeaf60297dc2a9f0f50a182c7339fe7a5bdaa2ee41808e859093d68.jpg) + +Carrying on from [[san francisco to fort bragg | yesterday]], we woke up early and got ready to ride the stretch up to Weaverville. The view from the hotel this morning was absolutely stunning - the fog was slowly burning off as the sun was brightening up, and the valley was echoing with seal calls. They were less atmospheric at 3am last night, though. + +I've entirely shaken off the jet lag at this point, and slept through the 06:30 without any major difficulties 🥳 It only took two nights to get used to the new timezone, so not bad! + +![](https://img.ellie.wtf/i/33da2adfba0dd3bd0ec0023caa9c3c84770166f4beb0ecc7b368a08362fb6cda.jpeg) + +Despite my best efforts, my gear was still very soggy from the night before - and the tiny hotel hair dryer didn’t so much to help. Luckily later in the day was nice and sunny so it dried out pretty fast + +We stopped for breakfast at a cute coffee shop - I had waffles 😋 There was an awful lot of fog this morning, and at times I couldn’t see far at all. It only really abated when we turned inland and started to head away from the coast + +![](https://img.ellie.wtf/i/aeec219bed70d75ab790246f95bec52a8e4b9f7f8ba51b06c587dc2726d83933.jpeg) + + +After turning off, we spent a good while riding through forest. There was no fog, but it was dense enough and the road dirty enough that I was being super careful not to slide out. + +Some of the redwoods are impressively large, though the biggest I saw was at a tourist spot. Turns out, in America, they even have drive-thru trees! + +![](https://img.ellie.wtf/i/b234bbf77cc36f6c63ce8855a8a24f6b59fce6fdc143670e680f90d7d368d407.jpeg) + +Our next stop was Garberville, and it was the last one before many miles without petrol stations. We filled up with “gas”, and snacks. It was at this point that Michelle’s key snapped out of the holder, so we also had to pick up some pliars + +The roads from this point were even better - endless mountains and views! Cows were free roaming (and would just sit in the road), and I lost count of how many deer we saw. They’re very nervous around bikes, so it’s best to slow right down + +We turned onto Highway 36, which was fantastic fun. It was very smooth, and the right mix of twisty and fast. We also saw a very large number of Porsches, which seem to be the vehicle of choice around there if you’re not driving a giant truck. + +![](https://img.ellie.wtf/i/ad7dd73b5a79300d43169c47a19d3ac7fef02f64b26f9b788472ac2e54c4b2b8.jpeg) + +We were aiming to have lunch at the Mad River Burger Bar, but it was closed 😞 I was very glad I had emergency snacks from earlier! The bar next door was open, and I left a signed dollar: + +![](https://img.ellie.wtf/i/a8fc4d438dd9d9d3ddd1bc5ae45385ba37a1e7407bb932f5cc69bbe27b97871a.jpeg) + +The final stretch to the Airbnb was all twisty roads, and highway 3 among the best of them so far. It’s starting to feel like we’re properly in the countryside now, further away from the bigger cities. + +I'd seen a lot of wild fire warnings on google maps, and a lot of signs saying that the fire risk today was "moderate". At one point, we were riding through an area that had been burned probably a few years back. It was sad to see the blackened skeletons of thousands of trees, especially knowing that this happens every year + +![](https://img.ellie.wtf/i/2ea431cfbcbd088aab0494a04535e864365237d71a3790005ae8c69738b6a8d5.jpg) + +The Airbnb we're staying in is a little outside of Weaverville, so it's peaceful and quiet. It's a cabin in the forest, with a nice outside seating area + +![](https://img.ellie.wtf/i/1d875369b4b02669a077439c778531937f996b5c769c8ebfa64ee515ebb810cc.jpg) + +We will be staying here and exploring for a few days, so picked up some groceries. Some learnings: + - Hard cider is alcoholic, normal cider might not be but isn’t just apple juice? + - it’s pronounced VITE-A-MINS + - Everything is bigger than back home - other than the bread, which was tiny + - A whole section in the supermarket for cookie dough! + - During fire season you’re not even allowed an indoor fire in the stove + - $10.99 for a tiny box of PG tips!!!! Before tax. I should start a business importing tea + +As the sun was going down, we walked down the road towards the river + +![](https://img.ellie.wtf/i/0f71907c18cbd404f45b5fd70c02c0dec24588bb4cc0f39667d0a330d8b6fbe1.jpg) + +![](https://img.ellie.wtf/i/596a84f42c35dcdb6df2b9edab54109c0d198f632dc94a81758b4c9540a2b7e8.jpg) + +I'm super super tired while writing this, so I imagine I'll be getting an early night tonight 😴 We covered 205 miles today! + From 73d118fbbe0a3cc7273f5982d808c028879fb05c Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 26 Sep 2023 20:46:14 -0700 Subject: [PATCH 21/94] Quartz sync: Sep 26, 2023, 8:46 PM --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a8790789..9aca71d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.0.11", + "version": "4.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.0.11", + "version": "4.1.0", "license": "MIT", "dependencies": { "@clack/prompts": "^0.6.3", From d8e2df8ac47cca1f60edfa0f4ce3b13e2deac614 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 26 Sep 2023 20:50:44 -0700 Subject: [PATCH 22/94] Quartz sync: Sep 26, 2023, 8:50 PM --- content/life/ft bragg to weaverville.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/content/life/ft bragg to weaverville.md b/content/life/ft bragg to weaverville.md index a29aef95..825657fd 100644 --- a/content/life/ft bragg to weaverville.md +++ b/content/life/ft bragg to weaverville.md @@ -10,11 +10,11 @@ date: 2023-09-26 Carrying on from [[san francisco to fort bragg | yesterday]], we woke up early and got ready to ride the stretch up to Weaverville. The view from the hotel this morning was absolutely stunning - the fog was slowly burning off as the sun was brightening up, and the valley was echoing with seal calls. They were less atmospheric at 3am last night, though. -I've entirely shaken off the jet lag at this point, and slept through the 06:30 without any major difficulties 🥳 It only took two nights to get used to the new timezone, so not bad! +I've entirely shaken off the jet lag at this point, and slept through to 06:30 without any major difficulties 🥳 It only took two nights to get used to the new timezone, so not bad! ![](https://img.ellie.wtf/i/33da2adfba0dd3bd0ec0023caa9c3c84770166f4beb0ecc7b368a08362fb6cda.jpeg) -Despite my best efforts, my gear was still very soggy from the night before - and the tiny hotel hair dryer didn’t so much to help. Luckily later in the day was nice and sunny so it dried out pretty fast +Despite my best efforts, my gear was still very soggy from being caught in the rain last night - and the tiny hotel hair dryer didn’t do much to help. Luckily later in the day was nice and sunny so it dried out pretty fast We stopped for breakfast at a cute coffee shop - I had waffles 😋 There was an awful lot of fog this morning, and at times I couldn’t see far at all. It only really abated when we turned inland and started to head away from the coast @@ -23,7 +23,7 @@ We stopped for breakfast at a cute coffee shop - I had waffles 😋 There was an After turning off, we spent a good while riding through forest. There was no fog, but it was dense enough and the road dirty enough that I was being super careful not to slide out. -Some of the redwoods are impressively large, though the biggest I saw was at a tourist spot. Turns out, in America, they even have drive-thru trees! +Some of the redwoods are impressively large, the biggest I saw was at a tourist spot. Turns out, in America, they even have drive-thru trees! ![](https://img.ellie.wtf/i/b234bbf77cc36f6c63ce8855a8a24f6b59fce6fdc143670e680f90d7d368d407.jpeg) @@ -50,12 +50,13 @@ The Airbnb we're staying in is a little outside of Weaverville, so it's peaceful ![](https://img.ellie.wtf/i/1d875369b4b02669a077439c778531937f996b5c769c8ebfa64ee515ebb810cc.jpg) We will be staying here and exploring for a few days, so picked up some groceries. Some learnings: - - Hard cider is alcoholic, normal cider might not be but isn’t just apple juice? - - it’s pronounced VITE-A-MINS - - Everything is bigger than back home - other than the bread, which was tiny - - A whole section in the supermarket for cookie dough! - - During fire season you’re not even allowed an indoor fire in the stove - - $10.99 for a tiny box of PG tips!!!! Before tax. I should start a business importing tea + +- Hard cider is alcoholic, normal cider might not be but isn’t just apple juice? +- it’s pronounced VITE-A-MINS +- Everything is bigger than back home - other than the bread, which was tiny +- A whole section in the supermarket for cookie dough! +- During fire season you’re not even allowed an indoor fire in the stove +- $10.99 for a tiny box of PG tips!!!! Before tax. I should start a business importing tea As the sun was going down, we walked down the road towards the river From 13db4c5fbe3addbaa99e6500d553666f8e9fee0d Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 26 Sep 2023 21:24:45 -0700 Subject: [PATCH 23/94] Quartz sync: Sep 26, 2023, 9:24 PM --- content/life/ft bragg to weaverville.md | 21 +++++++++++++-------- quartz/styles/custom.scss | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/content/life/ft bragg to weaverville.md b/content/life/ft bragg to weaverville.md index 825657fd..ffb2e26b 100644 --- a/content/life/ft bragg to weaverville.md +++ b/content/life/ft bragg to weaverville.md @@ -18,14 +18,14 @@ Despite my best efforts, my gear was still very soggy from being caught in the r We stopped for breakfast at a cute coffee shop - I had waffles 😋 There was an awful lot of fog this morning, and at times I couldn’t see far at all. It only really abated when we turned inland and started to head away from the coast -![](https://img.ellie.wtf/i/aeec219bed70d75ab790246f95bec52a8e4b9f7f8ba51b06c587dc2726d83933.jpeg) + After turning off, we spent a good while riding through forest. There was no fog, but it was dense enough and the road dirty enough that I was being super careful not to slide out. Some of the redwoods are impressively large, the biggest I saw was at a tourist spot. Turns out, in America, they even have drive-thru trees! -![](https://img.ellie.wtf/i/b234bbf77cc36f6c63ce8855a8a24f6b59fce6fdc143670e680f90d7d368d407.jpeg) + Our next stop was Garberville, and it was the last one before many miles without petrol stations. We filled up with “gas”, and snacks. It was at this point that Michelle’s key snapped out of the holder, so we also had to pick up some pliars @@ -33,11 +33,13 @@ The roads from this point were even better - endless mountains and views! Cows w We turned onto Highway 36, which was fantastic fun. It was very smooth, and the right mix of twisty and fast. We also saw a very large number of Porsches, which seem to be the vehicle of choice around there if you’re not driving a giant truck. -![](https://img.ellie.wtf/i/ad7dd73b5a79300d43169c47a19d3ac7fef02f64b26f9b788472ac2e54c4b2b8.jpeg) +We were aiming to have lunch at the Mad River Burger Bar, but it was closed 😞 I was very glad I had emergency snacks from earlier! The bar next door was open, and I left a signed dollar -We were aiming to have lunch at the Mad River Burger Bar, but it was closed 😞 I was very glad I had emergency snacks from earlier! The bar next door was open, and I left a signed dollar: +
+ + -![](https://img.ellie.wtf/i/a8fc4d438dd9d9d3ddd1bc5ae45385ba37a1e7407bb932f5cc69bbe27b97871a.jpeg) +
The final stretch to the Airbnb was all twisty roads, and highway 3 among the best of them so far. It’s starting to feel like we’re properly in the countryside now, further away from the bigger cities. @@ -47,7 +49,7 @@ I'd seen a lot of wild fire warnings on google maps, and a lot of signs saying t The Airbnb we're staying in is a little outside of Weaverville, so it's peaceful and quiet. It's a cabin in the forest, with a nice outside seating area -![](https://img.ellie.wtf/i/1d875369b4b02669a077439c778531937f996b5c769c8ebfa64ee515ebb810cc.jpg) + We will be staying here and exploring for a few days, so picked up some groceries. Some learnings: @@ -60,9 +62,12 @@ We will be staying here and exploring for a few days, so picked up some grocerie As the sun was going down, we walked down the road towards the river -![](https://img.ellie.wtf/i/0f71907c18cbd404f45b5fd70c02c0dec24588bb4cc0f39667d0a330d8b6fbe1.jpg) +
+ -![](https://img.ellie.wtf/i/596a84f42c35dcdb6df2b9edab54109c0d198f632dc94a81758b4c9540a2b7e8.jpg) + + +
I'm super super tired while writing this, so I imagine I'll be getting an early night tonight 😴 We covered 205 miles today! diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss index 8ef24222..d6bc4490 100644 --- a/quartz/styles/custom.scss +++ b/quartz/styles/custom.scss @@ -4,6 +4,21 @@ article p, article li{ line-height: 1.5 !important; } +.tall-img { + display: block; + margin-left: auto; + margin-right: auto; + width: 50%; +} + +.img-pair img { + display: inline-block; + width: 48%; +} + +.img-pair img:nth-child(even) { + float: right; +} @media (max-width: 480px) { .me { @@ -22,3 +37,4 @@ article p, article li{ align-items: center; } } + From aaa1425e194f3643a42af736221869eba8ad33f8 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 27 Sep 2023 22:02:11 -0700 Subject: [PATCH 24/94] Quartz sync: Sep 27, 2023, 10:02 PM --- content/life/mt shasta.md | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 content/life/mt shasta.md diff --git a/content/life/mt shasta.md b/content/life/mt shasta.md new file mode 100644 index 00000000..c6f12282 --- /dev/null +++ b/content/life/mt shasta.md @@ -0,0 +1,57 @@ +--- +title: Mt Shasta +created: 2023-09-27 +tags: + - usa + - wc23 +cover: https://img.ellie.wtf/i/fc823c94b9c741f147857522284a1221de915da5174deb34ad413798dcfb254e.jpg +--- +![](https://img.ellie.wtf/i/77e3db766d18e651f2c8e469375cb857312779e3c5d34b09b9707ddc38a2f0d4.jpeg) + +I'm writing this one after a longggg day of riding, including heading home in the dark with a tinted visor 😬 Probably going to keep it brief. Today we rode 280 miles! + +Today I woke up around 6:30, made some tea, and went to sit outside while the world wakes up. There are so many woodpecker sounds here! We had planned to ride to Mt Shasta, which has been one of the things I really wanted to see on this trip. + +Once Michelle was awake, we headed into Weaverville for breakfast. We went to this place called "Mamma Llama", and I learned that an "English Muffin" is indeed what we call a "Muffin" in England, and not the English version of something American. + +On the way up highway 3, we stopped by a lake. I probably could have sat by the water all day long + +![](https://img.ellie.wtf/i/6d52a2abe726e51c6020f8d2649afbb007fb3317f4c22b1aa4f426220bcb6895.jpg) + + +The whole time we'd been riding through the forest, I'd had to dodge squirrels every few miles. They dash out super quickly across the road, which explains why there were so many dead ones all over the place. Despite my best efforts, I hit one just after leaving the lake :( Sorry little squirrel! + +This part of the road was also especially dirty, and the banks showed evidence of landslides. In places the surface was almost entirely covered in clay + +We spent a good while riding through the mountains, and it felt like the road would go on forever. It was quiet, twisty, and had an excellent surface. The only other people we saw were semi-regular road works, and trucks for servicing those works. + +One thing I saw was that they have "pilot trucks" to escort you past all the works. In some cases we rode on totally unpaved roads, that were very rough and dirty. Back home, they'd probably just close the road - but here, I guess there are few alternate routes. + +
+ + +
+ +We rounded another corner, and there it was! A snowy peak poking above the trees. + +![](https://img.ellie.wtf/i/fc823c94b9c741f147857522284a1221de915da5174deb34ad413798dcfb254e.jpg) + +The next stop was a town called Weed. It was well positioned for a fuel stop before heading to Mt Shasta. There were a lot of gift stores selling T shirts with "I ❤️ Weed" written on them + +The ride up the mountain was fantastic, with the main issue being paying attention to the road - as we climbed, the views just kept getting better. Eventually the temperature dropped to 12C/55F, making me very glad I was wearing thermals. + +We rode up as high as was possible, and found lots of people camping with some people seeming to be praying. Apparently the mountain holds some sort of spiritual meaning to some people, and has a particularly sacred meaning to Native Americans. I'm not sure on the details there though, beyond what I can Google. + +![](https://img.ellie.wtf/i/0d104f69bd993357b026734eae1be9fda6b99b4900bf841539199d2dcd372755.jpg) + +![](https://img.ellie.wtf/i/7912a1826ab2de6f9b7779c0e3a171e85d177a6de4286bbec16b0514246b603d.jpg) + +On the way home, we stopped once more in Weed for fuel and snacks. We also explored the local florist! + +After that, we had about 2 hours riding until dinner. Michelle had planned the route to take us Forest Road 17. It reminded me of the country lanes in the UK, except with much more pine trees and mountains. There had also been several rockslides, with some boulders on the road around corners. We had a few final glimpses of Mt Shasta, before it grew too distant + +![](https://img.ellie.wtf/i/54647e2d6537b136019c3c895b8bf8e0407d80909f04beaca912bb599e85b84a.jpg) + +Highway 3 was the main route home, and we had a really lucky run - absolutely no cars, just one flowing corner after another. + +We wrapped up the day with dinner at Cafe on Main in Weaverville, which served _excellent_ food. \ No newline at end of file From 0afba81428190be1ca431a85d9f37ad2df837099 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 10 Oct 2023 19:41:59 -0700 Subject: [PATCH 25/94] Quartz sync: Oct 10, 2023, 7:41 PM --- .../exporting prometheus metrics with axum.md | 101 ------- content/notes/hetzner k3s.md | 251 ++++++++++++++++++ package-lock.json | 3 +- 3 files changed, 253 insertions(+), 102 deletions(-) delete mode 100644 content/notes/exporting prometheus metrics with axum.md create mode 100644 content/notes/hetzner k3s.md diff --git a/content/notes/exporting prometheus metrics with axum.md b/content/notes/exporting prometheus metrics with axum.md deleted file mode 100644 index fd0a1c1d..00000000 --- a/content/notes/exporting prometheus metrics with axum.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -permalink: notes/exporting-prometheus-metrics-with-axum -draft: false -tags: - - rust -date: 2023-09-13 -title: Exporting Prometheus metrics with Axum ---- -Observability is important! Generally I use Axum as my HTTP framework in [[rust|Rust]], as it's pretty ergonomic to use + fast. - -> [!info] -> [tower-http](https://github.com/tower-rs/tower-http) provides a bunch of useful HTTP middlewares used in a lot of projects. At the moment it does not provide a metrics middleware. Someday it may do! -> -> [Issue](https://github.com/tower-rs/tower-http/issues/57) to track - -There are quite a few crates that do a lot of this automagically for you, but the [Axum example](https://github.com/tokio-rs/axum/blob/368c3ee08fc3896358d3bd2bfc8cc67f2925c6ef/examples/prometheus-metrics/src/main.rs) suggests using [metrics](https://github.com/metrics-rs/metrics). I honestly don't need anything extra complex and just want a `/metrics` endpoint with some counters/etc most of the time - so metrics it is! - -Anyway, first we need to setup the prometheus exporter. This is basically what generates the content of `/metrics`. It uses the `metrics-exporter-prometheus` crate. You'll only want to set up a global recorder in an executable - for a library, you can leave that up to the user. - -I've mostly just lifted this from the Axum example linked above 😇 - -```rust -fn setup_metrics_recorder() -> PrometheusHandle { - const EXPONENTIAL_SECONDS: &[f64] = &[ - 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, - ]; - - PrometheusBuilder::new() - .set_buckets_for_metric( - Matcher::Full("http_requests_duration_seconds".to_string()), - EXPONENTIAL_SECONDS, - ) - .unwrap() - .install_recorder() - .unwrap() -} - -``` - -I'm actually setting up two metrics today, but only `history_requests_duration_seconds` requires some setup. This is because it is a histogram, and we need to tell the exporter how to bucket the data. - -Once that's done, we can write the axum middleware! (lifted from the example, and modified to compile properly. I'll open a PR) - -```rust -/// Middleware to record some common HTTP metrics -/// Generic over B to allow for arbitrary body types (eg Vec, Streams, a deserialized thing, etc) -/// Someday tower-http might provide a metrics middleware: https://github.com/tower-rs/tower-http/issues/57 -pub async fn track_metrics(req: Request, next: Next)->impl IntoResponse { - let start = Instant::now(); - - let path = if let Some(matched_path) = req.extensions().get::() { - matched_path.as_str().to_owned() - } else { - req.uri().path().to_owned() - }; - - let method = req.method().clone(); - - // Run the rest of the request handling first, so we can measure it and get response - // codes. - let response = next.run(req).await; - - let latency = start.elapsed().as_secs_f64(); - let status = response.status().as_u16().to_string(); - - let labels = [ - ("method", method.to_string()), - ("path", path), - ("status", status), - ]; - - metrics::increment_counter!("http_requests_total", &labels); - metrics::histogram!("http_requests_duration_seconds", latency, &labels); - - response -} - -``` - -Then, wherever you setup your Axum router, plug in the `/metrics` route! You'll need to make sure it's not publicly available. - -> [!note] -> -> I learned about `std::future::ready` here! It basically creates a future that is immediately available with a value. For example: -> -> ```rust ->let f = std::future::ready(1); -> assert_eq!(a.await, 1); -> ``` - - -```rust -let recorder_handle = setup_metrics_recorder(); - -let router = Router::new() - .route("/metrics", get(move || ready(recorder_handle.render()))) - .layer(axum::middleware::from_fn(track_metrics)); - -``` - -That's pretty much it really! \ No newline at end of file diff --git a/content/notes/hetzner k3s.md b/content/notes/hetzner k3s.md new file mode 100644 index 00000000..230dcd1f --- /dev/null +++ b/content/notes/hetzner k3s.md @@ -0,0 +1,251 @@ +--- +title: Setting up k3s on Hetzner Cloud +date: 2023-10-08 +tags: + - infra + - kubernetes +cover: https://img.ellie.wtf/i/a9e030bb8d6d703c69f113f7ee1fd69096849a3d29d611334f7cd10e13c2a4d5.jpg +--- + +I setup a HA k3s cluster for [[atuin | Atuin]] recently! + +I'm using HA etcd, which means we need to run an odd number of "server" nodes, and obviously more than one of them. That makes 3 the minimum. + +The servers are all setup in a private network, in their own subnet, and are ARM instances. The firewall is setup to disallow almost all ingress, and most egress. + +Honestly I'm shocked at how easy this was. I may have messed something up I'm unaware of, but my experience with `kubeadm` a few years back was _not_ as nice. Yay for k3s! +### Sources +I read a bunch of stuff. + +This guide was a nice start: https://community.hetzner.com/tutorials/k3s-glusterfs-loadbalancer + +But the k3s docs are really good, and I mostly just read those: https://docs.k3s.io/ + +I didn't use one of those (probably very good) automated hetzner k3s setup tools. I wanted to make sure I properly understood what was going on, and I've ran kubernetes on metal a few times in the past. Just with kubeadm, not k3s. +## Servers +The first server! Note that k3s calls nodes running the control plane a "server", and other nodes an "agent". By default it allows the master nodes to also schedule normal workloads, which is probably fine for my use case. + +I've disabled the cloud controller as we're installing a special hetzner one, and also disabled local storage as I'm going to be using Longhorn. Be sure to pick a good token! + +Otherwise, as I enabled private networking on my Hetzner + want my cluster to use that, I've pointed flannel towards the private network interface. + +```bash +curl -sfL https://get.k3s.io | sh -s - server \ + --cluster-init \ + --disable-cloud-controller \ + --disable local-storage \ + --node-name="$(hostname -f)" \ + --flannel-iface=enp7s0 \ + --kubelet-arg="cloud-provider=external" \ + --secrets-encryption \ + --disable=traefik \ + --token=CHANGE ME +``` + +Subsequent machines run a very similar command + +```bash +curl -sfL https://get.k3s.io | sh -s - server \ + --server SERVER ADDRESS \ + --disable-cloud-controller \ + --disable local-storage \ + --node-name="$(hostname -f)" \ + --flannel-iface=enp7s0 \ + --kubelet-arg="cloud-provider=external" \ + --secrets-encryption \ + --disable=traefik \ + --token=CHANGE ME +``` + +Please note: + +1. Check that the network interface is correct +2. Generate a token securely +3. Check that cloud-provider=external is still required. In kubernetes v.1.29+ this may not be the case. +4. Only the first server setup requires "cluster init". After that, you already have a cluster - no more init! + +> [!info] +> The first time I got all the way to setting up Hetzner Cloud Controller until I saw that in their docs they require you to pass the `--kubelet-arg="cloud-provider=external"` flag to each node. +> +> For most flags, you can just re-run the installer and it adjusts the config and restarts the node. For this flag in particular, if you miss it, you'll need to setup your cluster again. HCCM will only label nodes that were setup correctly from the very beginning, and unlabelled nodes won't work correctly with your LB. +> + Some background on this. Kubernetes has a bunch of CCMs (cloud controller managers) that basically integrate k8s nicely with a cloud provider. In order to install an external CCM, we currently need to set the aforementioned flag. However, this has been deprecated for quite some time. It was originally intended to be removed in v1.24, though this has not yet happened. +> +> My understanding is that currently kubelet is bundled with some CCMs, so this flag allows you to use a non-bundled CCM. The future plans are to no longer bundle CCMs, making this argument redundant (hence deprecation) +> +> Issue if you're interested in learning more: https://github.com/kubernetes/kubernetes/issues/110018 +> +> According to the linked PR, this may be included in v1.29. SO if you're running v1.29+, you might not want the cloud provider flag! + +At this point, you can run + +```bash +kubectl get nodes +``` + +from any of the machines setup, and get something like this: + +``` +NAME STATUS ROLES AGE VERSION +server-1 Ready control-plane,etcd,master 4m4s v1.27.6+k3s1 +server-2 Ready control-plane,etcd,master 47s v1.27.6+k3s1 +server-3 Ready control-plane,etcd,master 19s v1.27.6+k3s1 +``` + +And also check on the [secret encryption](https://docs.k3s.io/security/secrets-encryption) status with + +```bash +k3s secrets-encrypt status +``` + +``` +Encryption Status: Enabled +Current Rotation Stage: start +Server Encryption Hashes: All hashes match + +Active Key Type Name +------ -------- ---- + * AES-CBC aescbckey +``` + +Sweet! +## Access +Before doing more setup, I wanted to setup `kubectl` access from my laptop. Running commands directly from the nodes themselves felt gross. + +Once setup is fully complete I'll be setting up Tailscale (or maybe innernet) for access, but for now I'll just ssh port forward. You can get the kubeconfig via + +``` +cat /etc/rancher/k3s/k3s.yaml +``` + +on one of the nodes. + +A quick + +``` +ssh -L 6443:localhost:6443 root@a server ip +``` + +and you're good to use `kubectl` from your local device. Do setup something a little more robust though 😊 +## Hetzner Cloud Controller Manager +Try saying that 3 times really fast. Anyway [hccm](https://github.com/hetznercloud/hcloud-cloud-controller-manager) integrates our cluster with the hetzner cloud API, which means we can (stolen from the README): + +1. adds the server type to the `node.kubernetes.io/instance-type` label, sets the external ipv4 and ipv6 addresses and deletes nodes from Kubernetes that were deleted from the Hetzner Cloud. +2. makes Kubernetes aware of the failure domain of the server by setting the `topology.kubernetes.io/region` and `topology.kubernetes.io/zone` labels on the node. +3. allows to use Hetzner Cloud Private Networks for your pods traffic. +4. allows to use Hetzner Cloud Load Balancers with Kubernetes Services + +The hetzner blog post recommended just applying a manifest, _but_ the hccm docs recommend a helm chart. I've set it up with private networking enabled (why you would run this on a public network idk, maybe don't?) + +```bash +helm repo add hcloud https://charts.hetzner.cloud +helm repo update hcloud +``` + +You then need to set a k8s secret containing the hetzner cloud api token and network name (this is part of why I wanted to make sure secrets were encrypted at rest) + +``` +kubectl -n kube-system create secret generic hcloud --from-literal=token=SOME SECRET --from-literal=network=NETWORK NAME +``` + +```bash +helm install hccm hcloud/hcloud-cloud-controller-manager -n kube-system --set networking.enabled=true --set networking.clusterCIDR=10.42.0.0/16 +``` + +Do note the setting of the clusterCIDR. If you haven't changed the [k3s defaults](https://docs.k3s.io/cli/server#networking), 10.42.0.0/16 is good. + +``` +kubectl logs -n kube-system deployment/hcloud-cloud-controller-manager +``` + +Should now show some output, and + +``` +kubectl describe node agent-1 +``` + +should show some extra info annotations: + +``` +node.kubernetes.io/instance-type=cax21 +topology.kubernetes.io/region=fsn1 +topology.kubernetes.io/zone=fsn1-dc14 +``` + +## Agents +The default k3s behaviour is to actually schedule all deployments on nodes, so you may not need many of these (if that behaviour is OK with you) + +Setup is pretty similar to the servers! Just with less config. You'll need your token from before too + +```bash +curl -sfL https://get.k3s.io | sh -s - agent \ + --server SERVER ADDRESS \ + --node-name="$(hostname -f)" \ + --flannel-iface=enp7s0 \ + --kubelet-arg="cloud-provider=external" \ + --token=CHANGE ME +``` + +You can setup as many of these as you'd like + +``` +kubectl get nodes +``` + +``` +NAME STATUS ROLES AGE VERSION +agent-1 Ready 9s v1.27.6+k3s1 +server-1 Ready control-plane,etcd,master 100m v1.27.6+k3s1 +server-2 Ready control-plane,etcd,master 96m v1.27.6+k3s1 +server-3 Ready control-plane,etcd,master 96m v1.27.6+k3s1 +``` + +## Ingress with Traefik +Earlier, I setup the nodes so that traefik was disabled. But now I'm setting it up? + +Basically, the default k3s Traefik will use its own load balancer. There's nothing wrong with this really, but I wanted to make sure hccm was managing my LB. This means I get a proper cloud LB, with all the targets automatically managed by my cluster. + +It took a bit of messing to get working (I'd made some configuration errors detailed below) + +```bash +helm repo add traefik https://traefik.github.io/charts +helm repo update +``` + +I then created `traefik.values.yaml`. I'm not going to paste the whole thing here - generally I get the default values, save them to file, and then edit as I see fit. It's also probably a good idea for you to set things up how you want! + +I will say that these annotations are very important on your service: + +```yaml +service: + enabled: true + ## -- Single service is using `MixedProtocolLBService` feature gate. + ## -- When set to false, it will create two Service, one for TCP and one for UDP. + single: true + type: LoadBalancer + # -- Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) + annotations: + load-balancer.hetzner.cloud/location: fsn1 + load-balancer.hetzner.cloud/name: lb + load-balancer.hetzner.cloud/use-private-ip: "true" + +``` + +First we set the location of the lb. Give it a name. And then _tell it to use private ips_. I did not have this option at first, and my load balancer didn't work! All the targets were unhealthy. + +By default, hccm was only adding the public IP addresses to the LB. My firewall was blocking it (no public ingress straight to nodes), so nothing was routing ok. With this change, all was well 😇 + +I'm also using Cloudflare to terminate my SSL. I'll probably setup cert-manager at some point, but I am trying to keep my cluster as stateless as possible. Plus it's nice/easy to terminate SSL at the edge. + +## Next steps + +This is now at the point where I can deploy stateless services + point DNS at them with Cloudflare! + +There's a bunch I'd like to do next, although things are working fairly well now + +1. Setup storage with Longhorn, as right now we have no storage +2. Monitoring setup +3. Cloud init for automated agent setup +4. Easy VPN access +5. Extra Security hardening + diff --git a/package-lock.json b/package-lock.json index 9aca71d1..e4208c49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,7 +85,8 @@ "typescript": "^5.0.4" }, "engines": { - "node": ">=18.14" + "node": ">=18.14", + "npm": ">=9.3.1" } }, "node_modules/@clack/core": { From d3a05740dc3ffe19dc7f104964f2cf27ba2978a5 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 10 Oct 2023 19:43:35 -0700 Subject: [PATCH 26/94] recover post --- .../exporting prometheus metrics with axum.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 content/notes/exporting prometheus metrics with axum.md diff --git a/content/notes/exporting prometheus metrics with axum.md b/content/notes/exporting prometheus metrics with axum.md new file mode 100644 index 00000000..fd0a1c1d --- /dev/null +++ b/content/notes/exporting prometheus metrics with axum.md @@ -0,0 +1,101 @@ +--- +permalink: notes/exporting-prometheus-metrics-with-axum +draft: false +tags: + - rust +date: 2023-09-13 +title: Exporting Prometheus metrics with Axum +--- +Observability is important! Generally I use Axum as my HTTP framework in [[rust|Rust]], as it's pretty ergonomic to use + fast. + +> [!info] +> [tower-http](https://github.com/tower-rs/tower-http) provides a bunch of useful HTTP middlewares used in a lot of projects. At the moment it does not provide a metrics middleware. Someday it may do! +> +> [Issue](https://github.com/tower-rs/tower-http/issues/57) to track + +There are quite a few crates that do a lot of this automagically for you, but the [Axum example](https://github.com/tokio-rs/axum/blob/368c3ee08fc3896358d3bd2bfc8cc67f2925c6ef/examples/prometheus-metrics/src/main.rs) suggests using [metrics](https://github.com/metrics-rs/metrics). I honestly don't need anything extra complex and just want a `/metrics` endpoint with some counters/etc most of the time - so metrics it is! + +Anyway, first we need to setup the prometheus exporter. This is basically what generates the content of `/metrics`. It uses the `metrics-exporter-prometheus` crate. You'll only want to set up a global recorder in an executable - for a library, you can leave that up to the user. + +I've mostly just lifted this from the Axum example linked above 😇 + +```rust +fn setup_metrics_recorder() -> PrometheusHandle { + const EXPONENTIAL_SECONDS: &[f64] = &[ + 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, + ]; + + PrometheusBuilder::new() + .set_buckets_for_metric( + Matcher::Full("http_requests_duration_seconds".to_string()), + EXPONENTIAL_SECONDS, + ) + .unwrap() + .install_recorder() + .unwrap() +} + +``` + +I'm actually setting up two metrics today, but only `history_requests_duration_seconds` requires some setup. This is because it is a histogram, and we need to tell the exporter how to bucket the data. + +Once that's done, we can write the axum middleware! (lifted from the example, and modified to compile properly. I'll open a PR) + +```rust +/// Middleware to record some common HTTP metrics +/// Generic over B to allow for arbitrary body types (eg Vec, Streams, a deserialized thing, etc) +/// Someday tower-http might provide a metrics middleware: https://github.com/tower-rs/tower-http/issues/57 +pub async fn track_metrics(req: Request, next: Next)->impl IntoResponse { + let start = Instant::now(); + + let path = if let Some(matched_path) = req.extensions().get::() { + matched_path.as_str().to_owned() + } else { + req.uri().path().to_owned() + }; + + let method = req.method().clone(); + + // Run the rest of the request handling first, so we can measure it and get response + // codes. + let response = next.run(req).await; + + let latency = start.elapsed().as_secs_f64(); + let status = response.status().as_u16().to_string(); + + let labels = [ + ("method", method.to_string()), + ("path", path), + ("status", status), + ]; + + metrics::increment_counter!("http_requests_total", &labels); + metrics::histogram!("http_requests_duration_seconds", latency, &labels); + + response +} + +``` + +Then, wherever you setup your Axum router, plug in the `/metrics` route! You'll need to make sure it's not publicly available. + +> [!note] +> +> I learned about `std::future::ready` here! It basically creates a future that is immediately available with a value. For example: +> +> ```rust +>let f = std::future::ready(1); +> assert_eq!(a.await, 1); +> ``` + + +```rust +let recorder_handle = setup_metrics_recorder(); + +let router = Router::new() + .route("/metrics", get(move || ready(recorder_handle.render()))) + .layer(axum::middleware::from_fn(track_metrics)); + +``` + +That's pretty much it really! \ No newline at end of file From beba98e05bd785f81741c962781733f8aa1e29cb Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 10 Oct 2023 19:46:34 -0700 Subject: [PATCH 27/94] Fix callout --- content/notes/hetzner k3s.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/notes/hetzner k3s.md b/content/notes/hetzner k3s.md index 230dcd1f..b97104f9 100644 --- a/content/notes/hetzner k3s.md +++ b/content/notes/hetzner k3s.md @@ -6,7 +6,6 @@ tags: - kubernetes cover: https://img.ellie.wtf/i/a9e030bb8d6d703c69f113f7ee1fd69096849a3d29d611334f7cd10e13c2a4d5.jpg --- - I setup a HA k3s cluster for [[atuin | Atuin]] recently! I'm using HA etcd, which means we need to run an odd number of "server" nodes, and obviously more than one of them. That makes 3 the minimum. @@ -69,7 +68,7 @@ Please note: > > For most flags, you can just re-run the installer and it adjusts the config and restarts the node. For this flag in particular, if you miss it, you'll need to setup your cluster again. HCCM will only label nodes that were setup correctly from the very beginning, and unlabelled nodes won't work correctly with your LB. > - Some background on this. Kubernetes has a bunch of CCMs (cloud controller managers) that basically integrate k8s nicely with a cloud provider. In order to install an external CCM, we currently need to set the aforementioned flag. However, this has been deprecated for quite some time. It was originally intended to be removed in v1.24, though this has not yet happened. +> Some background on this. Kubernetes has a bunch of CCMs (cloud controller managers) that basically integrate k8s nicely with a cloud provider. In order to install an external CCM, we currently need to set the aforementioned flag. However, this has been deprecated for quite some time. It was originally intended to be removed in v1.24, though this has not yet happened. > > My understanding is that currently kubelet is bundled with some CCMs, so this flag allows you to use a non-bundled CCM. The future plans are to no longer bundle CCMs, making this argument redundant (hence deprecation) > From 59b91f4666fafe4494d10eee8524c93745f081f0 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 10 Oct 2023 19:47:39 -0700 Subject: [PATCH 28/94] highlighting --- content/notes/hetzner k3s.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/content/notes/hetzner k3s.md b/content/notes/hetzner k3s.md index b97104f9..ee973d76 100644 --- a/content/notes/hetzner k3s.md +++ b/content/notes/hetzner k3s.md @@ -113,7 +113,7 @@ Before doing more setup, I wanted to setup `kubectl` access from my laptop. Runn Once setup is fully complete I'll be setting up Tailscale (or maybe innernet) for access, but for now I'll just ssh port forward. You can get the kubeconfig via -``` +```bash cat /etc/rancher/k3s/k3s.yaml ``` @@ -121,7 +121,7 @@ on one of the nodes. A quick -``` +```bash ssh -L 6443:localhost:6443 root@a server ip ``` @@ -143,7 +143,7 @@ helm repo update hcloud You then need to set a k8s secret containing the hetzner cloud api token and network name (this is part of why I wanted to make sure secrets were encrypted at rest) -``` +```bash kubectl -n kube-system create secret generic hcloud --from-literal=token=SOME SECRET --from-literal=network=NETWORK NAME ``` @@ -153,19 +153,19 @@ helm install hccm hcloud/hcloud-cloud-controller-manager -n kube-system --set ne Do note the setting of the clusterCIDR. If you haven't changed the [k3s defaults](https://docs.k3s.io/cli/server#networking), 10.42.0.0/16 is good. -``` +```bash kubectl logs -n kube-system deployment/hcloud-cloud-controller-manager ``` Should now show some output, and -``` +```bash kubectl describe node agent-1 ``` should show some extra info annotations: -``` +```yaml node.kubernetes.io/instance-type=cax21 topology.kubernetes.io/region=fsn1 topology.kubernetes.io/zone=fsn1-dc14 @@ -187,7 +187,7 @@ curl -sfL https://get.k3s.io | sh -s - agent \ You can setup as many of these as you'd like -``` +```bash kubectl get nodes ``` From ec87577e9652374413ab111937e68a5e3428c345 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 10 Oct 2023 19:53:57 -0700 Subject: [PATCH 29/94] Quartz sync: Oct 10, 2023, 7:53 PM --- content/notes/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/notes/index.md b/content/notes/index.md index 6a2c694a..969ce608 100644 --- a/content/notes/index.md +++ b/content/notes/index.md @@ -1,4 +1,6 @@ --- title: Notes --- -A collection of my notes. These are better browsed by tag or by links, as otherwise all topics will be mixed. \ No newline at end of file +A collection of my notes. These are better browsed by tag or by links, as otherwise all topics will be mixed. + +I don't put as much effort into polishing these as I do for my [posts](/posts)! I usually write them while working on something, clean them up a bit, and then publish. \ No newline at end of file From dd17592c81fff75d9f4d4ae570855c258571331b Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 10 Oct 2023 19:55:56 -0700 Subject: [PATCH 30/94] Quartz sync: Oct 10, 2023, 7:55 PM --- content/life/mt shasta.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/life/mt shasta.md b/content/life/mt shasta.md index c6f12282..04455ff7 100644 --- a/content/life/mt shasta.md +++ b/content/life/mt shasta.md @@ -1,6 +1,6 @@ --- title: Mt Shasta -created: 2023-09-27 +date: 2023-09-27 tags: - usa - wc23 From 0ada3b31c809ce71f66120437c3f050b4e862959 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 13 Oct 2023 19:45:06 +0100 Subject: [PATCH 31/94] Quartz sync: Oct 13, 2023, 7:45 PM --- quartz/components/Footer.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/quartz/components/Footer.tsx b/quartz/components/Footer.tsx index 54440cff..85f121ed 100644 --- a/quartz/components/Footer.tsx +++ b/quartz/components/Footer.tsx @@ -13,9 +13,6 @@ export default ((opts?: Options) => { return ( ) } From 11aaa45f8b1a4fc3ce51c48055b2551cad363a4c Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 13 Oct 2023 19:48:04 +0100 Subject: [PATCH 32/94] Quartz sync: Oct 13, 2023, 7:48 PM --- quartz/components/Footer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/quartz/components/Footer.tsx b/quartz/components/Footer.tsx index 85f121ed..b6cf9eb3 100644 --- a/quartz/components/Footer.tsx +++ b/quartz/components/Footer.tsx @@ -20,6 +20,7 @@ export default ((opts?: Options) => { ))} +
From a61148b3cc61efd787d1613f51b276cc5a2abcd4 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 25 Oct 2023 18:37:46 +0100 Subject: [PATCH 33/94] Quartz sync: Oct 25, 2023, 6:37 PM --- content/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/index.md b/content/index.md index 4e5e3479..85f18f82 100644 --- a/content/index.md +++ b/content/index.md @@ -36,7 +36,7 @@ Previously, I've worked at [Coinbase](https://coinbase.com), [Tracr](https://tra ## Contact Please do get in touch! -Email: ellie @ \
+Email: ellie@elliehuxtable.com
GitHub: [@ellie](https://github.com/ellie)
Mastodon: [@ellie@hachyderm.io](https://hachyderm.io/@ellie)
Twitter: [@ellie_huxtable](https://twitter.com/ellie_huxtable)
\ No newline at end of file From 163fc5879755c5d916e8dcd1b49e9b112d6ffded Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Oct 2023 08:09:23 +0000 Subject: [PATCH 34/94] Quartz sync: Oct 31, 2023, 8:09 AM --- content/notes/postgres on zfs.md | 255 +++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 content/notes/postgres on zfs.md diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md new file mode 100644 index 00000000..dfa2e757 --- /dev/null +++ b/content/notes/postgres on zfs.md @@ -0,0 +1,255 @@ +--- +date: 2023-10-31 +tags: + - postgresql + - zfs +title: Running PostgeSQL on ZFS on baremetal +--- + +I'm setting up new postgres servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. + +You might also be interested in the [[hetzner k3s]] setup I did for the Atuin api images + +I'm going to be doing a fairly minimal setup to begin with, and tune things later. I'm not sure which options will best suit my workload, so will keep things simple. + +Note that the Atuin queries are not complicated. We mostly just store a pretty high volume of data, and read sequentially (ish). There are minimal joins, and minimal complex queries. + +We will often have bursts where 10s/100s of thousands of rows need to be written or read as quickly as possible - however this is also pretty sequential and not at all "complex". + +Ideally, we will compress the data as much as possible. While Atuin mostly stores encrypted data (which does not compress well), there is a decent amount of unencrypted data in the form of JSON padding + timestamps etc. Compress them! This will nicely reduce disk usage, but also IO - at the cost of some CPU. + +As our queries are simple, the CPU cost is fine. + +## Setting up +I'm running this on a couple of hetzner machines I bought in an auction. They have Ryzen CPUs, and 4x 1tb nvme SSD. + +Two of the SSDs will be running a simple RAID mirror, and will store the OS and logs. The other two will have my ZFS filesystem + postgres on them. + +This costs me 50% of my storage in mirroring, however as this is bare metal hardware there's a chance that a disk could fail. I'd like to ensure my database can continue running until I failover to the replica + a technician can replace the disk. + +```bash +# Install zfs +apt install zfsutils-linux + +# check version +zfs version +``` + +Check your disk layout with `lsblk` + +``` +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS +nvme1n1 259:0 0 953.9G 0 disk +├─nvme1n1p1 259:1 0 32G 0 part +│ └─md0 9:0 0 32G 0 raid1 [SWAP] +├─nvme1n1p2 259:2 0 1G 0 part +│ └─md1 9:1 0 1022M 0 raid1 /boot +├─nvme1n1p3 259:3 0 128G 0 part +│ └─md2 9:2 0 127.9G 0 raid1 /var +├─nvme1n1p4 259:4 0 1K 0 part +└─nvme1n1p5 259:5 0 792.9G 0 part + └─md3 9:3 0 792.7G 0 raid1 / +nvme0n1 259:6 0 953.9G 0 disk +nvme3n1 259:7 0 953.9G 0 disk +nvme2n1 259:8 0 953.9G 0 disk +├─nvme2n1p1 259:9 0 32G 0 part +│ └─md0 9:0 0 32G 0 raid1 [SWAP] +├─nvme2n1p2 259:10 0 1G 0 part +│ └─md1 9:1 0 1022M 0 raid1 /boot +├─nvme2n1p3 259:11 0 128G 0 part +│ └─md2 9:2 0 127.9G 0 raid1 /var +├─nvme2n1p4 259:12 0 1K 0 part +└─nvme2n1p5 259:13 0 792.9G 0 part + └─md3 9:3 0 792.7G 0 raid1 / +``` + +`/dev/nvme1n1` and `/dev/nvme2n1` are both in use for my OS, but `/dev/nvme0n1` and `/dev/nvme3n1` are leftover for my zfs mirror. + +Create the mirror + +```bash +zpool create postgres mirror /dev/nvme0n1 /dev/nvme3n1 +``` + +It returned nice and fast! + +Checking again with `lsblk` shows some usage + +``` +nvme0n1 259:6 0 953.9G 0 disk +├─nvme0n1p1 259:14 0 953.9G 0 part +└─nvme0n1p9 259:15 0 8M 0 part +nvme3n1 259:7 0 953.9G 0 disk +├─nvme3n1p1 259:18 0 953.9G 0 part +└─nvme3n1p9 259:19 0 8M 0 part +``` + +Confirmed with `zpool status` + +``` + pool: postgres + state: ONLINE +config: + + NAME STATE READ WRITE CKSUM + postgres ONLINE 0 0 0 + mirror-0 ONLINE 0 0 0 + nvme0n1 ONLINE 0 0 0 + nvme3n1 ONLINE 0 0 0 + +errors: No known data errors +``` + +The pool is mounted by default at `/postgres`. Nice! + +## Postgres + +Before we can setup datasets, we need to setup postgres. Postgres needs to run its init _before_ we move it to zfs. Annoying, but ok. + +Ubuntu 22.04 has a pretty old version in its repos, so install the postgres one + +```bash +sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +apt update +``` + + +```bash +apt install postgresql-16 postgresql-contrib-16 +systemctl enable postgresql +systemctl start postgresql +``` + +Then, stop postgres. Move its data to a temp location, create the datasets, and move things back again. Could probably rsync instead of mv/cp to keep permissions. + +``` +systemctl stop postgresql + +# move postgres data to temp +mv /var/lib/postgresql/16/main/pg_wal /tmp/pg_wal +mv /var/lib/postgresql /tmp/postgresql + +# create the datasets +zfs create postgres/data -o mountpoint=/var/lib/postgresql +zfs create postgres/wal -o mountpoint=/var/lib/postgresql/16/main/pg_wal + +# switcharoo the data back +cp -r /tmp/postgresql/* /var/lib/postgresql +cp -r /tmp/pg_wal/* /var/lib/postgresql/16/main/pg_wal + +# sort perms +chmod -R 0700 /var/lib/postgresql +chmod -R 0700 /var/lib/postgresql/16/main/pg_wal +chown -R postgres: /var/lib/postgresql + +# start postgres once more +systemctl start postgresql +``` + +Check all is ok: + +```bash +zfs list + +NAME USED AVAIL REFER MOUNTPOINT +postgres 600K 922G 24K /postgres +postgres/db 72K 922G 24K /postgres/db +postgres/db/base 24K 922G 24K /postgres/db/base +postgres/db/pg_wal 24K 922G 24K /postgres/db/pg_wal +``` + +## Configuring ZFS + +I've read a bunch, including: + +- https://vadosware.io/post/everything-ive-seen-on-optimizing-postgres-on-zfs-on-linux/ +- https://bun.uptrace.dev/postgres/tuning-zfs-aws-ebs.html + +One of the above articles had issues where some nvme hardware was reporting a successful write when using `fdatasync`, but not actually writing ok. Many drives will report a successful write once the data has been stored in the volatile write cache, but not actually stored. Disable this with: + +``` +apt install nvme-cli +nvme set-feature -f 6 -v 0 /dev/nvme0n1 +nvme set-feature -f 6 -v 0 /dev/nvme3n1 +``` + +I spend a while thinking about the optimal `recordsize`. While I'd get a higher tps running recordsize = postgres block size = 8kb, Atuin is largely sequential + reads/writes large amounts of data. I could change the postgres block size (it's a compilation option), but I'll consider trying that in the future. + +To begin with, I'll try 16k and see how it goes. We should get improved tps vs 128k, less write amplification, but better compression vs 8k. + +``` +# enable compression +zfs set compression=zstd-3 postgres + +# disable access time (so, so many writes...) +zfs set atime=off postgres + +# enable improved extended attributes +zfs set xattr=sa postgres + +zfs set recordsize=16k postgres +``` + +I then set `full_page_writes = off` on the postgres side - zfs cannot write partial pages so it's pretty redundant. + +Next up, we'll give the ARC (ZFS page cache) 75% of the system memory. The remainder will be used by postgres shared_buffers. + +``` +echo 25769803776 >> /sys/module/zfs/parameters/zfs_arc_max +``` + +To persist across reboots, set this in `/etc/modprobe.d/zfs.conf` + +``` +options zfs zfs_arc_max=25769803776 +``` + +## Postgres config + +Not specific to ZFS, but some general postgres tuning + +``` +# 25% of 32GB +shared_buffers = 8GB + +work_mem = 8MB + +# make vaccuums/etc faster +maintenance_work_mem = 1GB + +# tell the planner how much the ZFS ARC will likely cache +effective_cache_size = 24GB +``` + +There's a bunch more tuning we can do, but realistically this will help the most. Postgres has a pretty tiny amount of memory configured by default! + +At this point, I rebooted to ensure everything was OK + persisted properly. + +## Chuck load at it + +I'd like to ensure my system performs at least mostly ok, so I ran pgbench and got + +``` +scaling factor: 50 +query mode: simple number of clients: 20 +number of threads: 4 +maximum number of tries: 1 +number of transactions per client: 100000 +number of transactions actually processed: 2000000/2000000 +number of failed transactions: 0 (0.000%) +latency average = 2.863 ms +initial connection time = 10.287 ms +tps = 6984.804317 (without initial connection time) +``` + +Not bad! Not that representative of my workload, but at least an indication it's not totally broken. I'll tune this some more in the future. + +## Next + +Next I'll be configuring backups with pgbackrest, and setting up a hot standby we can failover to should things go wrong! + +Then copying the dataset across, and making this new database production 🚀 + From 9421a8378672e0d4c665c86be9e8b87e70a519d7 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Oct 2023 08:10:42 +0000 Subject: [PATCH 35/94] Quartz sync: Oct 31, 2023, 8:10 AM --- content/notes/postgres on zfs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index dfa2e757..0e82d563 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -3,7 +3,7 @@ date: 2023-10-31 tags: - postgresql - zfs -title: Running PostgeSQL on ZFS on baremetal +title: Running bare metal PostgeSQL on ZFS --- I'm setting up new postgres servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. From d7a56ebdc05833b4d48fdd7f05be039f43f3a58f Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Oct 2023 08:11:48 +0000 Subject: [PATCH 36/94] Quartz sync: Oct 31, 2023, 8:11 AM --- content/notes/postgres on zfs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index 0e82d563..50bbf8cf 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -3,7 +3,7 @@ date: 2023-10-31 tags: - postgresql - zfs -title: Running bare metal PostgeSQL on ZFS +title: Running bare metal PostgreSQL on ZFS --- I'm setting up new postgres servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. From b4cca227d6a1f010b29a9cff33598d29c939fefe Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Oct 2023 08:13:47 +0000 Subject: [PATCH 37/94] Quartz sync: Oct 31, 2023, 8:13 AM --- content/notes/postgres on zfs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index 50bbf8cf..70df2ed9 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -125,7 +125,7 @@ systemctl start postgresql Then, stop postgres. Move its data to a temp location, create the datasets, and move things back again. Could probably rsync instead of mv/cp to keep permissions. -``` +```bash systemctl stop postgresql # move postgres data to temp @@ -180,7 +180,7 @@ I spend a while thinking about the optimal `recordsize`. While I'd get a higher To begin with, I'll try 16k and see how it goes. We should get improved tps vs 128k, less write amplification, but better compression vs 8k. -``` +```bash # enable compression zfs set compression=zstd-3 postgres From b45167079c8469e1451e887d1730413853dccc93 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Oct 2023 08:15:07 +0000 Subject: [PATCH 38/94] Quartz sync: Oct 31, 2023, 8:15 AM --- content/notes/postgres on zfs.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index 70df2ed9..58e4eff7 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -4,6 +4,7 @@ tags: - postgresql - zfs title: Running bare metal PostgreSQL on ZFS +cover: https://img.ellie.wtf/i/74b97825c93824bf34b2fac6982d43a51ea62914ce9c8f8f7e3459b2ba78cd4b.png --- I'm setting up new postgres servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. From 1036564ac7b91ab12036ca472fd19d441269eca8 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Oct 2023 12:33:28 +0000 Subject: [PATCH 39/94] Quartz sync: Oct 31, 2023, 12:33 PM --- content/notes/postgres on zfs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index 58e4eff7..e0838cd4 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -69,7 +69,7 @@ nvme2n1 259:8 0 953.9G 0 disk Create the mirror ```bash -zpool create postgres mirror /dev/nvme0n1 /dev/nvme3n1 +zpool create postgres mirror -o ashift=12 /dev/nvme0n1 /dev/nvme3n1 ``` It returned nice and fast! From 3ca8feb04d271619153dcf83080c0f38b0b19189 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 1 Nov 2023 12:20:04 +0000 Subject: [PATCH 40/94] Quartz sync: Nov 1, 2023, 12:20 PM --- content/index.md | 23 ++- content/notes/postgres on zfs.md | 256 +++++++++++++++++++++++++++ content/notes/tuning zfs postgres.md | 3 + 3 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 content/notes/postgres on zfs.md create mode 100644 content/notes/tuning zfs postgres.md diff --git a/content/index.md b/content/index.md index 4e5e3479..9b7afa50 100644 --- a/content/index.md +++ b/content/index.md @@ -8,7 +8,7 @@ date: 2023-09-01 My name is Ellie Huxtable. I'm a software/infrastructure engineer, and am at my happiest when I'm building something cool. I love an adventure, and if I'm not at a computer there's a good chance I'm riding a motorcycle. -Here I am trying to maintain a personal wiki, or a second brain. I often explore many technologies or ideas, and then promptly completely forget them. I've found that writing my learnings and thoughts down is immensely helpful. +Here I am trying to maintain a personal wiki, or a second brain. I've found that writing my learnings and thoughts down is immensely helpful.
@@ -22,11 +22,28 @@ This site is constantly shifting, but here are some things you may be interested - [Posts](/posts), a selection of my longer-form writing and thoughts - [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod | iPod]] and [[atuin | Atuin]]. - [Life](/life), where I'm writing about my travels, adventures, and life in general -- I also keep some [notes](/notes) on a variety of topics! It may be easier to browse those by tag. +- [Notes](/notes), where I write about things I'm working on and exploring. They're not supposed to be as high-quality as a post, but I'll be publishing them much more often. ## Speaking I gave my first talk at [FOSDEM in 2023](https://www.youtube.com/watch?v=uyRmV19qJ2o), and am looking forward to giving more in the future! +## Publicity +Some of my [projects](/projects) have been featured in print, in blogs, and in podcasts. This list is not exhaustive, please get in touch if I've missed something! + +### Atuin +- [Linux Magazine, issue 265](https://www.linux-magazine.com/Issues/2022/265/Atuin) +- [LinuxUser 09/2022 (a German magazine)](https://www.linux-community.de/ausgaben/linuxuser/2022/09/die-befehlshistorie-ueber-mehrere-rechner-hinweg-im-blick-behalten/) +- [Changelog 53](https://changelog.com/news/53) +- [an interview with console.dev](https://console.dev/interviews/atuin-ellie-huxtable) +- discussed on [LinuxMatters #10](https://linuxmatters.sh/10/) +- [a GitPod guide](https://www.gitpod.io/guides/persisted-terminal-history-atuin) +- [RustShip #3](https://www.marcoieni.com/2023/09/%EF%B8%8F-atuin-shell-history-sync-search-and-backup-ellie-huxtable-rustship-3/) + +### iPod +- [Vice, with an interview](https://www.vice.com/en/article/qjbexd/a-software-engineer-upgraded-an-old-ipod-for-2022?ref=ellie.wtf) +- [Hackaday](https://hackaday.com/2022/02/16/classic-ipods-are-super-upgradeable-in-2022/?ref=ellie.wtf) +- [TechSpot](https://www.techspot.com/community/topics/breathing-new-life-into-an-old-ipod-with-a-few-thoughtful-upgrades.273895/?ref=ellie.wtf) + ## Work I currently lead the infrastructure team at [PostHog](https://posthog.com/?ref=ellie.wtf), regularly working with Kubernetes and Terraform. @@ -36,7 +53,7 @@ Previously, I've worked at [Coinbase](https://coinbase.com), [Tracr](https://tra ## Contact Please do get in touch! -Email: ellie @ \
+Email: ellie@elliehuxtable.com
GitHub: [@ellie](https://github.com/ellie)
Mastodon: [@ellie@hachyderm.io](https://hachyderm.io/@ellie)
Twitter: [@ellie_huxtable](https://twitter.com/ellie_huxtable)
\ No newline at end of file diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md new file mode 100644 index 00000000..9521029a --- /dev/null +++ b/content/notes/postgres on zfs.md @@ -0,0 +1,256 @@ +--- +date: 2023-10-31 +tags: + - postgresql + - zfs +title: Running bare metal PostgreSQL on ZFS +cover: https://img.ellie.wtf/i/74b97825c93824bf34b2fac6982d43a51ea62914ce9c8f8f7e3459b2ba78cd4b.png +--- + +I'm setting up new postgres servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. + +You might also be interested in the [[hetzner k3s]] setup I did for the Atuin api images + +I'm going to be doing a fairly minimal setup to begin with, and tune things later. I'm not sure which options will best suit my workload, so will keep things simple. + +Note that the Atuin queries are not complicated. We mostly just store a pretty high volume of data, and read sequentially (ish). There are minimal joins, and minimal complex queries. + +We will often have bursts where 10s/100s of thousands of rows need to be written or read as quickly as possible - however this is also pretty sequential and not at all "complex". + +Ideally, we will compress the data as much as possible. While Atuin mostly stores encrypted data (which does not compress well), there is a decent amount of unencrypted data in the form of JSON padding + timestamps etc. Compress them! This will nicely reduce disk usage, but also IO - at the cost of some CPU. + +As our queries are simple, the CPU cost is fine. + +## Setting up +I'm running this on a couple of hetzner machines I bought in an auction. They have Ryzen CPUs, and 4x 1tb nvme SSD. + +Two of the SSDs will be running a simple RAID mirror, and will store the OS and logs. The other two will have my ZFS filesystem + postgres on them. + +This costs me 50% of my storage in mirroring, however as this is bare metal hardware there's a chance that a disk could fail. I'd like to ensure my database can continue running until I failover to the replica + a technician can replace the disk. + +```bash +# Install zfs +apt install zfsutils-linux + +# check version +zfs version +``` + +Check your disk layout with `lsblk` + +``` +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS +nvme1n1 259:0 0 953.9G 0 disk +├─nvme1n1p1 259:1 0 32G 0 part +│ └─md0 9:0 0 32G 0 raid1 [SWAP] +├─nvme1n1p2 259:2 0 1G 0 part +│ └─md1 9:1 0 1022M 0 raid1 /boot +├─nvme1n1p3 259:3 0 128G 0 part +│ └─md2 9:2 0 127.9G 0 raid1 /var +├─nvme1n1p4 259:4 0 1K 0 part +└─nvme1n1p5 259:5 0 792.9G 0 part + └─md3 9:3 0 792.7G 0 raid1 / +nvme0n1 259:6 0 953.9G 0 disk +nvme3n1 259:7 0 953.9G 0 disk +nvme2n1 259:8 0 953.9G 0 disk +├─nvme2n1p1 259:9 0 32G 0 part +│ └─md0 9:0 0 32G 0 raid1 [SWAP] +├─nvme2n1p2 259:10 0 1G 0 part +│ └─md1 9:1 0 1022M 0 raid1 /boot +├─nvme2n1p3 259:11 0 128G 0 part +│ └─md2 9:2 0 127.9G 0 raid1 /var +├─nvme2n1p4 259:12 0 1K 0 part +└─nvme2n1p5 259:13 0 792.9G 0 part + └─md3 9:3 0 792.7G 0 raid1 / +``` + +`/dev/nvme1n1` and `/dev/nvme2n1` are both in use for my OS, but `/dev/nvme0n1` and `/dev/nvme3n1` are leftover for my zfs mirror. + +Create the mirror + +```bash +zpool create postgres mirror -o ashift=12 /dev/nvme0n1 /dev/nvme3n1 +``` + +It returned nice and fast! + +Checking again with `lsblk` shows some usage + +``` +nvme0n1 259:6 0 953.9G 0 disk +├─nvme0n1p1 259:14 0 953.9G 0 part +└─nvme0n1p9 259:15 0 8M 0 part +nvme3n1 259:7 0 953.9G 0 disk +├─nvme3n1p1 259:18 0 953.9G 0 part +└─nvme3n1p9 259:19 0 8M 0 part +``` + +Confirmed with `zpool status` + +``` + pool: postgres + state: ONLINE +config: + + NAME STATE READ WRITE CKSUM + postgres ONLINE 0 0 0 + mirror-0 ONLINE 0 0 0 + nvme0n1 ONLINE 0 0 0 + nvme3n1 ONLINE 0 0 0 + +errors: No known data errors +``` + +The pool is mounted by default at `/postgres`. Nice! + +## Postgres + +Before we can setup datasets, we need to setup postgres. Postgres needs to run its init _before_ we move it to zfs. Annoying, but ok. + +Ubuntu 22.04 has a pretty old version in its repos, so install the postgres one + +```bash +sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +apt update +``` + + +```bash +apt install postgresql-16 postgresql-contrib-16 +systemctl enable postgresql +systemctl start postgresql +``` + +Then, stop postgres. Move its data to a temp location, create the datasets, and move things back again. Could probably rsync instead of mv/cp to keep permissions. + +```bash +systemctl stop postgresql + +# move postgres data to temp +mv /var/lib/postgresql/16/main/pg_wal /tmp/pg_wal +mv /var/lib/postgresql /tmp/postgresql + +# create the datasets +zfs create postgres/data -o mountpoint=/var/lib/postgresql +zfs create postgres/wal -o mountpoint=/var/lib/postgresql/16/main/pg_wal + +# switcharoo the data back +cp -r /tmp/postgresql/* /var/lib/postgresql +cp -r /tmp/pg_wal/* /var/lib/postgresql/16/main/pg_wal + +# sort perms +chmod -R 0700 /var/lib/postgresql +chmod -R 0700 /var/lib/postgresql/16/main/pg_wal +chown -R postgres: /var/lib/postgresql + +# start postgres once more +systemctl start postgresql +``` + +Check all is ok: + +```bash +zfs list + +NAME USED AVAIL REFER MOUNTPOINT +postgres 600K 922G 24K /postgres +postgres/db 72K 922G 24K /postgres/db +postgres/db/base 24K 922G 24K /postgres/db/base +postgres/db/pg_wal 24K 922G 24K /postgres/db/pg_wal +``` + +## Configuring ZFS + +I've read a bunch, including: + +- https://vadosware.io/post/everything-ive-seen-on-optimizing-postgres-on-zfs-on-linux/ +- https://bun.uptrace.dev/postgres/tuning-zfs-aws-ebs.html + +One of the above articles had issues where some nvme hardware was reporting a successful write when using `fdatasync`, but not actually writing ok. Many drives will report a successful write once the data has been stored in the volatile write cache, but not actually stored. Disable this with: + +``` +apt install nvme-cli +nvme set-feature -f 6 -v 0 /dev/nvme0n1 +nvme set-feature -f 6 -v 0 /dev/nvme3n1 +``` + +I spend a while thinking about the optimal `recordsize`. While I'd get a higher tps running recordsize = postgres block size = 8kb, Atuin is largely sequential + reads/writes large amounts of data. I could change the postgres block size (it's a compilation option), but I'll consider trying that in the future. + +To begin with, I'll try the default of 128k and see how it goes. Lower numbers are potentially faster, but higher numbers tend to get better compression. It depends on a lot of factors though, so I'll measure things and see. + +```bash +# enable compression +zfs set compression=zstd-3 postgres + +# disable access time (so, so many writes...) +zfs set atime=off postgres + +# enable improved extended attributes +zfs set xattr=sa postgres + +# zfs set recordsize=16k postgres +``` + +I then set `full_page_writes = off` on the postgres side - zfs cannot write partial pages so it's pretty redundant. + +Next up, we'll give the ARC (ZFS page cache) 75% of the system memory. The remainder will be used by postgres shared_buffers. + +``` +echo 51539607552 >> /sys/module/zfs/parameters/zfs_arc_max +``` + +To persist across reboots, set this in `/etc/modprobe.d/zfs.conf` + +``` +options zfs zfs_arc_max=51539607552 +``` + +## Postgres config + +Not specific to ZFS, but some general postgres tuning + +``` +# 25% of 64GB +shared_buffers = 16GB + +work_mem = 8MB + +# make vaccuums/etc faster +maintenance_work_mem = 1GB + +# tell the planner how much the ZFS ARC will likely cache +effective_cache_size = 48GB +``` + +There's a bunch more tuning we can do, but realistically this will help the most. Postgres has a pretty tiny amount of memory configured by default! + +At this point, I rebooted to ensure everything was OK + persisted properly. + +## Chuck load at it + +I'd like to ensure my system performs at least mostly ok, so I ran pgbench and got + +``` +scaling factor: 50 +query mode: simple number of clients: 20 +number of threads: 4 +maximum number of tries: 1 +number of transactions per client: 100000 +number of transactions actually processed: 2000000/2000000 +number of failed transactions: 0 (0.000%) +latency average = 2.863 ms +initial connection time = 10.287 ms +tps = 6984.804317 (without initial connection time) +``` + +Not bad! Not that representative of my workload, but at least an indication it's not totally broken. I'll tune this some more in the future. + +## Next + +Next I'll be configuring backups with pgbackrest, and setting up a hot standby we can failover to should things go wrong! + +Then copying the dataset across, and making this new database production 🚀 + diff --git a/content/notes/tuning zfs postgres.md b/content/notes/tuning zfs postgres.md new file mode 100644 index 00000000..71b6dbe9 --- /dev/null +++ b/content/notes/tuning zfs postgres.md @@ -0,0 +1,3 @@ +- recordsize=128 not that bad, actually +- preferring a scan to index for some reason. `random_page_cost = 1` sorted that + - worked after running analyze and a couple of queries \ No newline at end of file From ac4aae8c5f4aa5736218114ac6e68e8091f0dbed Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 1 Nov 2023 12:30:12 +0000 Subject: [PATCH 41/94] Quartz sync: Nov 1, 2023, 12:30 PM --- content/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/index.md b/content/index.md index 9b7afa50..79f0d17b 100644 --- a/content/index.md +++ b/content/index.md @@ -20,7 +20,9 @@ Here I am trying to maintain a personal wiki, or a second brain. I've fou This site is constantly shifting, but here are some things you may be interested in - [Posts](/posts), a selection of my longer-form writing and thoughts -- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are my [[ipod | iPod]] and [[atuin | Atuin]]. +- [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are + - [Atuin](https://atuin.sh), the shell history search + sync tool + - [my iPod](/ipod), that I modified for much more storage + battery life - [Life](/life), where I'm writing about my travels, adventures, and life in general - [Notes](/notes), where I write about things I'm working on and exploring. They're not supposed to be as high-quality as a post, but I'll be publishing them much more often. From 850047d24f70ca6222ce7269738c144932f417de Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 1 Nov 2023 12:30:42 +0000 Subject: [PATCH 42/94] Quartz sync: Nov 1, 2023, 12:30 PM --- content/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/index.md b/content/index.md index 79f0d17b..ebd25352 100644 --- a/content/index.md +++ b/content/index.md @@ -22,7 +22,7 @@ This site is constantly shifting, but here are some things you may be interested - [Posts](/posts), a selection of my longer-form writing and thoughts - [Projects](/projects) - over the years I've built a lot of things, but the main two I'm known for are - [Atuin](https://atuin.sh), the shell history search + sync tool - - [my iPod](/ipod), that I modified for much more storage + battery life + - [my iPod](/projects/ipod), that I modified for much more storage + battery life - [Life](/life), where I'm writing about my travels, adventures, and life in general - [Notes](/notes), where I write about things I'm working on and exploring. They're not supposed to be as high-quality as a post, but I'll be publishing them much more often. From 6a7b6d94695659f771b501654eed2d05b43c457e Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 1 Nov 2023 12:35:31 +0000 Subject: [PATCH 43/94] Quartz sync: Nov 1, 2023, 12:35 PM --- content/notes/tuning zfs postgres.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/notes/tuning zfs postgres.md b/content/notes/tuning zfs postgres.md index 71b6dbe9..098a7d68 100644 --- a/content/notes/tuning zfs postgres.md +++ b/content/notes/tuning zfs postgres.md @@ -1,3 +1,6 @@ +--- +draft: true +--- - recordsize=128 not that bad, actually - preferring a scan to index for some reason. `random_page_cost = 1` sorted that - worked after running analyze and a couple of queries \ No newline at end of file From 8d39a16b8fc57fa2033e584d62a6d9ed4a0482e0 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 3 Nov 2023 20:22:17 +0000 Subject: [PATCH 44/94] Sync --- content/notes/postgres hba tailscale.md | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 content/notes/postgres hba tailscale.md diff --git a/content/notes/postgres hba tailscale.md b/content/notes/postgres hba tailscale.md new file mode 100644 index 00000000..ae670c49 --- /dev/null +++ b/content/notes/postgres hba tailscale.md @@ -0,0 +1,42 @@ +--- +title: Postgres HBA with a Tailscale network +tags: + - postgresql + - tailscale +date: 2023-11-03 +--- +Following on with my [[postgres on zfs]] setup, I needed to configure the auth so that my replica could securely connect to the primary. + +The constraints here are that I'm not using a cloud private network, so need a VPN of some kind! I'm using [Tailscale](https://tailscale.com), which is pretty much just wireguard made easy. + +## Firewall +First up, following Tailscale's [docs](https://tailscale.com/kb/1077/secure-server-ubuntu-18-04/). Make sure you don't lock yourself out! I'm also using tailscale SSH with 2fa. + +```bash +ufw allow in on tailscale0 +ufw default deny incoming +ufw default allow outgoing + +ufw reload + +systemctl restart sshd +``` + +## postgresql.conf +Next, we need to tell postgres to listen on all addresses. In your postgres config: + +``` +listen_addresses = '*' +``` + +I wish I could just provide `tailscale0`, the tailnet CIDR or... anything else. Alas. It's not that flexible, but at least we can lock things down with the firewall + hba. Don't skip the other steps! + +# pg_hba + +Next up we just need to setup pg_hba.conf to allow the login! + +``` +host all all 100.64.0.0/10 scram-sha-256 +``` + +Where `100.64.0.0/10` is the CIDR range used by Tailscale. Read more [here](https://tailscale.com/kb/1015/100.x-addresses/). \ No newline at end of file From bbb3169dea1595a6bcfb9afd7a4f984f2bb57df2 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 18 Nov 2023 15:39:36 +0000 Subject: [PATCH 45/94] Quartz sync: Nov 18, 2023, 3:39 PM --- content/notes/haproxy prometheus metrics.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 content/notes/haproxy prometheus metrics.md diff --git a/content/notes/haproxy prometheus metrics.md b/content/notes/haproxy prometheus metrics.md new file mode 100644 index 00000000..8f65f393 --- /dev/null +++ b/content/notes/haproxy prometheus metrics.md @@ -0,0 +1,14 @@ +I've recently started using haproxy after a lifetime of nginx, and I actually really like it! I needed to setup some metrics, when I discovered that haproxy supports prometheus out of the box - no exporter needed! 🥳 + +The documentation shows binding on whatever frontend you're already using, but I wanted to make sure my metrics and stats were not visible to anyone outside of my network + +``` +listen stats + bind :9000 + mode http + stats enable + stats uri / + http-request use-service prometheus-exporter if { path /metrics } +``` + +Pretty simple - we're adding a combined frontend/backend, bound on http 9000. The root shows the HAProxy metrics page, and if we hit `/metrics` then we show some prometheus metrics. So easy! \ No newline at end of file From 8fa512a75d3771b54adbb4785d4dffa5fa4287b3 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 18 Nov 2023 15:40:57 +0000 Subject: [PATCH 46/94] Quartz sync: Nov 18, 2023, 3:40 PM --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4208c49..e2b52b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.1.0", + "version": "4.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.1.0", + "version": "4.1.1", "license": "MIT", "dependencies": { "@clack/prompts": "^0.6.3", From a73d1d53f178b8ce9215bba7fa7b6f8b6674bb5b Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 18 Nov 2023 15:48:30 +0000 Subject: [PATCH 47/94] sync --- content/notes/haproxy prometheus metrics.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/content/notes/haproxy prometheus metrics.md b/content/notes/haproxy prometheus metrics.md index 8f65f393..34b144ac 100644 --- a/content/notes/haproxy prometheus metrics.md +++ b/content/notes/haproxy prometheus metrics.md @@ -1,3 +1,11 @@ +--- +title: Exporting Prometheus metrics from HAProxy +date: 2023-11-18 +tags: + - infra + - prometheus +--- + I've recently started using haproxy after a lifetime of nginx, and I actually really like it! I needed to setup some metrics, when I discovered that haproxy supports prometheus out of the box - no exporter needed! 🥳 The documentation shows binding on whatever frontend you're already using, but I wanted to make sure my metrics and stats were not visible to anyone outside of my network From 565ea0f057f75a3e35c81ee508a77e432f174f20 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sat, 18 Nov 2023 15:48:52 +0000 Subject: [PATCH 48/94] sync --- content/notes/haproxy prometheus metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/haproxy prometheus metrics.md b/content/notes/haproxy prometheus metrics.md index 34b144ac..a2020430 100644 --- a/content/notes/haproxy prometheus metrics.md +++ b/content/notes/haproxy prometheus metrics.md @@ -12,7 +12,7 @@ The documentation shows binding on whatever frontend you're already using, but I ``` listen stats - bind :9000 + bind *:9000 mode http stats enable stats uri / From 28f05a030463b04e8a94c108e8e482959fc80cbc Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sun, 19 Nov 2023 11:16:09 +0000 Subject: [PATCH 49/94] sync --- content/posts/social media and social life.md | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 content/posts/social media and social life.md diff --git a/content/posts/social media and social life.md b/content/posts/social media and social life.md new file mode 100644 index 00000000..00e7f4f6 --- /dev/null +++ b/content/posts/social media and social life.md @@ -0,0 +1,90 @@ +--- +title: Social media and social life +date: 2023-11-19 +cover: https://img.ellie.wtf/i/f6f5c27ad8f06074fd55b59989dde0bb88f7cdf1944f77740eb8c4b9afd2b43c.jpg +description: My attempts to maintain minimal social media usage without becoming hooked on an infinite-scrolling algorithm. +--- +Over the past year or so, I've been scaling back how much time and energy I put into social media. + +For a long time, I'd keep my Instagram story "up to date", regularly post on TikTok, and spend far too much of my day scrolling various feeds. Lately, I've been scaling that back. I know a number of people who've totally deleted their accounts, but for me that wasn't quite desirable. I didn't want to risk losing touch with people or what's going on. Much as I don't like it, a bunch of social organising has moved to social media. + +1. A number of geographically distant friends I keep in touch with almost exclusively over Instagram +2. I've found tech community on Twitter + Mastodon +3. It's useful for meeting new people while travelling +4. It can be useful for work + +Lots of my behaviours towards the consumption of social media were not what I regarded to be healthy. Much as it can be a useful tool, it is a tool that requires control; otherwise, it will use you. + +# Applying limits + +I started off trying to limit things. I figured I could retain some of what I saw as upsides while reducing the downsides if I stuck to say, 15-30 mins a day. +## Attempt #1: Screen time limits + +I initially setup Apple screen time limits. These did a pretty good job of stopping me from scrolling, for a few days anyway. + +After 30 minutes, the screen time limit pops up. Unfortunately, it's easy to bypass - my poor impulse control and big-tech addiction soon got used to dismissing this without even noticing. + +## Attempt #2: One sec + +Next up, I tried [One Sec](https://one-sec.app/). It essentially delays your access to apps, making you wait for a certain number of seconds before it is possible to open them. This actually worked pretty well! It interrupted the easy-dopamine reach of just opening social media for "a few seconds", and forced me to actually think about what I was doing. Most of the time, it resulted in me remembering that I wanted to spend my life differently and backing out. + +![](https://img.ellie.wtf/i/4fdb295d3303f0871d00782a3ef40157329f52c4f9698241c1cc2bb4ed99af56.jpg) + +After a while, I noticed a different pattern. Sometimes I'd open the app before I anticipated I'd actually want to use it, and other times I'd just use it for longer. My overall usage had gone down a whole bunch though, so I'd call this a partial success. + +# Just delete it + +The main thing holding me back from uninstalling Instagram and Twitter was the messengers. Especially with international friends, it's easier to swap social media than it is to request phone numbers - and often more socially acceptable. Even though I've put a great deal of effort into trying to move people to better alternatives, it's realistically not going to happen any time soon. + +If I tried going on a techie rant about how addicting social media is, how it's used to control groups of people, and how "if it's free, you're the product"... most of my not-tech friends would ignore me and think I was losing it. I admire those who stick so strongly to their principals there, but I'm not willing to pay the social cost. + +Recently I setup [Beeper](https://www.beeper.com/). It has done an amazing job of totally replacing social media apps for messaging. I can still keep in touch with people where I need to, but I don't end up getting sucked into a time vacuum. Fantastic! + +## TikTok + +The first thing I removed was TikTok. It had an uncanny ability to absolutely entrap my attention, for an embarrassingly long period of time. After an hours doom-scrolling, there was rarely (if ever) anything I could remember. The app is excellent at absorbing your time and contributing little to your life. The worst part was that most of the posts weren't anything at all from my real friends, just internet personalities and memes. + +For a while, I actually used to post videos - mostly relating to motorcycles. A few of these ended up with millions of views. TikTok analytics show the total amount of "human time" that has been spent, per video. It showed that videos I had uploaded, putting in very little effort, had been complicit in pointlessly absorbing _days_ of combined human lifespan. Across the platform, it must be measured in decades/centuries. + +Some people have said they manage to learn useful things from TikTok, but that wasn't really my experience. I'm also unsure if people convince themselves of this, so that they feel less bad about spending so much time on it. + +## Instagram + +After a while, I uninstalled the Instagram app entirely. I didn't delete my account, as I do still find it useful from time-to-time. I started using Instagram purely through the instagram.com web app, and it hasn't been anywhere near as addictive. + +I think the lack of polish on mobile (vs the apps) is just about jarring enough that I don't waste any time at all! I've actually found it interesting how the UI being "smooth" keeps me absorbed, but if it's jerkier/less native the likelihood of me becoming too absorbed decreases dramatically. It's usually the case that after a couple of minutes I just think "wow, there really isn't a lot here huh" and close the page. + +## Twitter + +I've kept Twitter for now. Much as I'm not a big fan of the platform, I still maintain connections with a bunch of people there, and it has been useful for my work. I'd like to see that change, but for now it is still the case. + +The app is still on my phone, but I think soon I'll be banishing it to a Firefox tab just like Instagram. + +## Facebook + +I honestly haven't used this in several years. I have an account, but it's inactive. Nothing to remove! + +## Mastodon + +I've found Mastodon to be very refreshing. The total lack of algorithm means I can very quickly have a quick read of what's been shared since my last visit, and then get back to my day. There's not new content every time the spinner loads. + +Generally (and perhaps that's not everyone's experience) I've found the community there to be much more pleasant. Less flamewars, less politics, more genuine humanity. + +I'll be keeping this one 😊 + +# Relaxing + +I've heard lots of people say that they like checking social media as a way to wind down, or to relax. Obviously I'm unable to speak for everyone, but for me personally there's a huge difference between conscious and intentional winding down, and switching your brain off while the magic box feeds you with whatever the algorithm deems best for engagement. One is restful, while the other burns time. + +# Conclusion + +After a few months, I've found that I have much more time. I've filled it with other things - more time outdoors, reading books much more often, and more time for projects. I'm also trying to learn the guitar. + +So far, I've been writing much more too! There's a whole bunch of drafts for my blog, and notes are being published pretty regularly. + +I no longer reach for my phone at the first sign of boredom or "empty time", instead using it purely as a tool. I think there's still work to do here, but it's a big improvement. + +In an ideal world, tech wouldn't have such a grip on our lives. But there's certainly compromises we can make, without having to go all out and delete everything. + +Who knows - maybe in a few months I'll publish something titled "deleting it all". + From 796b62f7027854d8042dc6288c7296644f5c5bb3 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sun, 19 Nov 2023 11:21:02 +0000 Subject: [PATCH 50/94] sync --- content/notes/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/notes/index.md b/content/notes/index.md index 969ce608..8a4e81af 100644 --- a/content/notes/index.md +++ b/content/notes/index.md @@ -1,5 +1,6 @@ --- title: Notes +date: 2023-09-01 --- A collection of my notes. These are better browsed by tag or by links, as otherwise all topics will be mixed. From e9e128d1b79fcc19b89e38221dd166ad9c5ac6bf Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sun, 19 Nov 2023 11:25:07 +0000 Subject: [PATCH 51/94] sync --- content/posts/social media and social life.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/posts/social media and social life.md b/content/posts/social media and social life.md index 00e7f4f6..e3540a5b 100644 --- a/content/posts/social media and social life.md +++ b/content/posts/social media and social life.md @@ -86,5 +86,3 @@ I no longer reach for my phone at the first sign of boredom or "empty time", ins In an ideal world, tech wouldn't have such a grip on our lives. But there's certainly compromises we can make, without having to go all out and delete everything. -Who knows - maybe in a few months I'll publish something titled "deleting it all". - From c51bf9f4c20ea6862770bce0c02c813f8d00e503 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sun, 19 Nov 2023 11:43:07 +0000 Subject: [PATCH 52/94] sync --- content/posts/social media and social life.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/posts/social media and social life.md b/content/posts/social media and social life.md index e3540a5b..bc3b7bfd 100644 --- a/content/posts/social media and social life.md +++ b/content/posts/social media and social life.md @@ -54,7 +54,9 @@ After a while, I uninstalled the Instagram app entirely. I didn't delete my acco I think the lack of polish on mobile (vs the apps) is just about jarring enough that I don't waste any time at all! I've actually found it interesting how the UI being "smooth" keeps me absorbed, but if it's jerkier/less native the likelihood of me becoming too absorbed decreases dramatically. It's usually the case that after a couple of minutes I just think "wow, there really isn't a lot here huh" and close the page. -## Twitter +I mentioned above, but [Beeper](https://www.beeper.com/) has really helped here. I can continue to message people who prefer staying in touch via Instagram, without actually having it installed on my phone. + +## Twitter / X I've kept Twitter for now. Much as I'm not a big fan of the platform, I still maintain connections with a bunch of people there, and it has been useful for my work. I'd like to see that change, but for now it is still the case. From feb89fedb8824f072537fc2bbf2d1fe4a9ca1e6a Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 28 Nov 2023 12:21:28 +0000 Subject: [PATCH 53/94] sync --- content/notes/sonoma broken internet.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 content/notes/sonoma broken internet.md diff --git a/content/notes/sonoma broken internet.md b/content/notes/sonoma broken internet.md new file mode 100644 index 00000000..c4660924 --- /dev/null +++ b/content/notes/sonoma broken internet.md @@ -0,0 +1,19 @@ +--- +title: Fixing a broken internet connection after upgrading to macOS 14 Sonoma +date: 2023-11-27 +--- +I upgraded my mac to macOS 14, Sonoma earlier today. Upon first using it, my internet wouldn't connect! + +Wifi or ethernet, nothing I tried work + +Even + +``` +ping 8.8.8.8 +``` + +I did some searching, and it turns out the version of [Little Snitch](https://www.obdev.at/products/littlesnitch/index.html) I had installed wasn't supported. + +The issue here is that without internet, I couldn't update it + +The fix wasn't too difficult. Go to System Settings -> Network -> VPNs and Filters. Disable little snitch. \ No newline at end of file From 5a170f2873cfc15d34d062af1938c186bf3fd388 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 28 Nov 2023 12:27:31 +0000 Subject: [PATCH 54/94] sync --- content/notes/sonoma broken internet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/sonoma broken internet.md b/content/notes/sonoma broken internet.md index c4660924..6adc7382 100644 --- a/content/notes/sonoma broken internet.md +++ b/content/notes/sonoma broken internet.md @@ -1,5 +1,5 @@ --- -title: Fixing a broken internet connection after upgrading to macOS 14 Sonoma +title: Upgrading to macOS 14 Sonoma broke my network connection date: 2023-11-27 --- I upgraded my mac to macOS 14, Sonoma earlier today. Upon first using it, my internet wouldn't connect! From 52e617a013ef03f8003384bfe48ef67575971f39 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 28 Nov 2023 12:30:57 +0000 Subject: [PATCH 55/94] disable explorer --- quartz.layout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/quartz.layout.ts b/quartz.layout.ts index ad4c835e..15780a7b 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -27,7 +27,6 @@ export const defaultContentPageLayout: PageLayout = { Component.MobileOnly(Component.Spacer()), Component.Search(), Component.Darkmode(), - Component.DesktopOnly(Component.Explorer()), Component.DesktopOnly(Component.RecentNotes()), ], right: [ From 356bd8021beed2b1e3fafc6877e1ef774dcacd7b Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 29 Dec 2023 12:29:57 +0000 Subject: [PATCH 56/94] Quartz sync: Dec 29, 2023, 12:29 PM --- content/Untitled.canvas | 1 + content/notes/backing up mastodon.md | 86 +++++++++++++++++++++++++ content/notes/postgres hba tailscale.md | 2 +- content/notes/postgres on zfs.md | 2 +- content/notes/postgresql.md | 1 - 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 content/Untitled.canvas create mode 100644 content/notes/backing up mastodon.md diff --git a/content/Untitled.canvas b/content/Untitled.canvas new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/content/Untitled.canvas @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/content/notes/backing up mastodon.md b/content/notes/backing up mastodon.md new file mode 100644 index 00000000..b880a537 --- /dev/null +++ b/content/notes/backing up mastodon.md @@ -0,0 +1,86 @@ +--- +title: Backing up mastodon +slug: mastodon-postgres-backup +date: 2023-12-29 +description: Automated mastodon postgres backups to an object store +tags: + - infra + - mastodon +--- +I'm the administrator of https://bikers.social, a mastodon instance for bikers 🏍️ + +My old mastodon backup policy consisted of regular `pg_dumps`, and `scp` to another machine. This was totally good enough, as that machine was _also_ backed up to my local NAS. I did have to semi-regularly manually clean up old backups though, and now that the instance has been going a while it would be great to have something better. + +I've recently revamped it a bit to use Cloudflare R2. + +Postgres is the most important part of an instance backup. If you lose your db, it's game over. My secrets are also backed up. +## R2 + +I use R2 for all of the instance's assets. It's much cheaper than S3 for storage, and also has no egress fees! I'd be a bit too concerned about surprise bandwidth bills with S3. + +As of May 2023, R2 also supports [lifecycle policies](https://developers.cloudflare.com/r2/buckets/object-lifecycles/) + +## Setup + +First up, make a new R2 bucket. This will be used for storage. Ensure it is not publicly accessible! + +Then we want to setup the local aws cli. I created an API token scoped to the bucket, with object read/write, and limited only to the IP address of my mastodon server. Run `aws configure`, and paste in your new account ID and secret. + +> [!note] +> The AWS cli will, by default, assume that the endpoint you wish to use is Amazon. Newer versions of the CLI can be configured in config, but chances are your package repository is out of date. For all subsequent commands, I set +> +> `alias aws='aws --endpoint-url https://.r2.cloudflarestorage.com'` + +We can then setup the lifecycle policy. + +Go to your bucket settings, then lifecycle policy + +![](https://img.ellie.wtf/i/ada76f57ae8dc3a95b7f3b9f3ef966b1e712e3f530b4f4820088ff1ba82574c5.png) + +I setup a lifecycle rule to keep daily backups for a week, weekly backups for 6 weeks, and monthly backups for 6 months. + +## Script + +My backup script is super simple. When run, it creates a file for the current day, and then uploads it to r2 with the specified prefix. + +```bash +#! /bin/bash + +prefix=${1:-daily} +date=$(date '+%Y-%m-%d') + +pg_dump -Fc -Z 0 -U mastodon mastodon_production | xz -T4 > $date.sql.xz + +aws --endpoint-url https://.r2.cloudflarestorage.com s3 cp ./$date.sql.xz s3:///$prefix/$date.sql.xz +``` + +If no prefix is specified, we assume daily backups. + +I've chosen `xz` for my backups. It produces some of the best compression, at cost of being very slow. My mastodon instance is pretty overkill in terms of spec, due to it being a hetzner box and my instance not being too high traffic. + +`pg_dump -Fc -Z 0` - using the custom format, but disabling compression as we're doing that with xz + +`xz -T4` will use xz compression, and no more than 4 threads. I tried `-T0` and it absolutely pegged all 16 threads, which was a bit excessive. 4 still completes fast enough. + +## Crontab +After testing it manually with + +``` +./backup.sh daily +./backup.sh weekly +./backup.sh monthly +``` + +and ensuring all files were created OK, I setup my crontab + +``` +0 12 * * * /home/mastodon/backup.sh daily +0 13 * * 0 /home/mastodon/backup.sh weekly +0 14 1 * * /home/mastodon/backup.sh monthly +``` + +There's some duplicate work, but that's not a huge concern here. I've scheduled them during "awake" hours for me, as I also set it up to ping me on Telegram when the backup completes. + +## Improvements + +In the future, I may change this to use [wal-g](https://github.com/wal-g/wal-g). I think this would be pretty overkill for what we have now, and the backup/restore process with `pg_dump` is super simple. If the database size gets to be too large, or if it makes sense to have point-in-time recovery, I'll revisit this. \ No newline at end of file diff --git a/content/notes/postgres hba tailscale.md b/content/notes/postgres hba tailscale.md index ae670c49..3e0bb2ff 100644 --- a/content/notes/postgres hba tailscale.md +++ b/content/notes/postgres hba tailscale.md @@ -5,7 +5,7 @@ tags: - tailscale date: 2023-11-03 --- -Following on with my [[postgres on zfs]] setup, I needed to configure the auth so that my replica could securely connect to the primary. +Following on with my [[postgres on zfs]] setup, I needed to configure the auth so that my [[postgresql | PostgreSQL]] replica could securely connect to the primary. The constraints here are that I'm not using a cloud private network, so need a VPN of some kind! I'm using [Tailscale](https://tailscale.com), which is pretty much just wireguard made easy. diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index 9521029a..4ddd7c2f 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -7,7 +7,7 @@ title: Running bare metal PostgreSQL on ZFS cover: https://img.ellie.wtf/i/74b97825c93824bf34b2fac6982d43a51ea62914ce9c8f8f7e3459b2ba78cd4b.png --- -I'm setting up new postgres servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. +I'm setting up new [[postgresql | postgres]] servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. You might also be interested in the [[hetzner k3s]] setup I did for the Atuin api images diff --git a/content/notes/postgresql.md b/content/notes/postgresql.md index 78f2d28b..881f575a 100644 --- a/content/notes/postgresql.md +++ b/content/notes/postgresql.md @@ -6,7 +6,6 @@ tags: - postgresql - infra --- - Some postgres snippets! Just a reference page for the things I forget a lot. - `pg_ctl init -D path` - init new database + config at `path` From d777e4e437b79aa7744c1a07003129e321fe45ca Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 29 Dec 2023 12:30:06 +0000 Subject: [PATCH 57/94] Quartz sync: Dec 29, 2023, 12:30 PM --- content/Untitled.canvas | 1 - 1 file changed, 1 deletion(-) delete mode 100644 content/Untitled.canvas diff --git a/content/Untitled.canvas b/content/Untitled.canvas deleted file mode 100644 index 9e26dfee..00000000 --- a/content/Untitled.canvas +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From 09f77f07b519daf7274838a80446d6bd15dba7e6 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 29 Dec 2023 12:35:01 +0000 Subject: [PATCH 58/94] Quartz sync: Dec 29, 2023, 12:35 PM --- content/notes/backing up mastodon.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/notes/backing up mastodon.md b/content/notes/backing up mastodon.md index b880a537..23769794 100644 --- a/content/notes/backing up mastodon.md +++ b/content/notes/backing up mastodon.md @@ -9,11 +9,11 @@ tags: --- I'm the administrator of https://bikers.social, a mastodon instance for bikers 🏍️ +Postgres is the most important part of an instance backup. If you lose your db, it's game over. My secrets are also backed up. + My old mastodon backup policy consisted of regular `pg_dumps`, and `scp` to another machine. This was totally good enough, as that machine was _also_ backed up to my local NAS. I did have to semi-regularly manually clean up old backups though, and now that the instance has been going a while it would be great to have something better. I've recently revamped it a bit to use Cloudflare R2. - -Postgres is the most important part of an instance backup. If you lose your db, it's game over. My secrets are also backed up. ## R2 I use R2 for all of the instance's assets. It's much cheaper than S3 for storage, and also has no egress fees! I'd be a bit too concerned about surprise bandwidth bills with S3. From 7bb92f3b74236c3fb930559a2cb9acf5e2018bc2 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 29 Dec 2023 12:37:42 +0000 Subject: [PATCH 59/94] Quartz sync: Dec 29, 2023, 12:37 PM --- content/notes/backing up mastodon.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/backing up mastodon.md b/content/notes/backing up mastodon.md index 23769794..2da778c8 100644 --- a/content/notes/backing up mastodon.md +++ b/content/notes/backing up mastodon.md @@ -79,7 +79,7 @@ and ensuring all files were created OK, I setup my crontab 0 14 1 * * /home/mastodon/backup.sh monthly ``` -There's some duplicate work, but that's not a huge concern here. I've scheduled them during "awake" hours for me, as I also set it up to ping me on Telegram when the backup completes. +There's some duplicate work, but that's not a huge concern here. I've scheduled them during waking hours for me, as I also set it up to ping me on Telegram when the backup completes. ## Improvements From fb16670c5b3573460a12b505da2e9f4d81579563 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 3 Jan 2024 22:43:47 +0000 Subject: [PATCH 60/94] Quartz sync: Jan 3, 2024, 10:43 PM --- content/notes/postgresql.md | 17 +++++++---- quartz.layout.ts | 2 ++ quartz/components/Links.tsx | 47 +++++++++++++++++++++++++++++ quartz/components/index.ts | 2 ++ quartz/components/styles/links.scss | 19 ++++++++++++ 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 quartz/components/Links.tsx create mode 100644 quartz/components/styles/links.scss diff --git a/content/notes/postgresql.md b/content/notes/postgresql.md index 881f575a..9d531f3f 100644 --- a/content/notes/postgresql.md +++ b/content/notes/postgresql.md @@ -5,6 +5,7 @@ title: PostgreSQL tags: - postgresql - infra +updated: 03/01/2024 --- Some postgres snippets! Just a reference page for the things I forget a lot. @@ -24,41 +25,45 @@ Some postgres snippets! Just a reference page for the things I forget a lot. - `\c dbname` connect to database as current user ### Create table as copy of another -``` +```sql create table new_table as table old_table; ``` Note: this will copy all data, but no indices or constraints For no data -``` +```sql create table new_table as table old_table with no data; ``` If you'd like to query/filter it: -``` +```sql create table new_table as (select * from old_table where some_condition); ``` ### Check for waiting locks -``` +```sql select relation::regclass, * from pg_locks where not granted; ``` ### Get database size -``` +```sql SELECT pg_size_pretty(pg_database_size('database name')); ``` +### Get table size +```sql +SELECT pg_size_pretty(pg_relation_size('records')); +``` ### Monitoring replication slots ``` SELECT * FROM pg_replication_slots; ``` ### Monitoring replication lag -``` +```sql SELECT extract(epoch from now() - pg_last_xact_replay_timestamp()) AS replica_lag ``` diff --git a/quartz.layout.ts b/quartz.layout.ts index 15780a7b..95beb278 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -27,6 +27,7 @@ export const defaultContentPageLayout: PageLayout = { Component.MobileOnly(Component.Spacer()), Component.Search(), Component.Darkmode(), + Component.DesktopOnly(Component.Links()), Component.DesktopOnly(Component.RecentNotes()), ], right: [ @@ -44,6 +45,7 @@ export const defaultListPageLayout: PageLayout = { Component.MobileOnly(Component.Spacer()), Component.Search(), Component.Darkmode(), + Component.DesktopOnly(Component.RecentNotes()), ], right: [], } diff --git a/quartz/components/Links.tsx b/quartz/components/Links.tsx new file mode 100644 index 00000000..4f5ca8f0 --- /dev/null +++ b/quartz/components/Links.tsx @@ -0,0 +1,47 @@ +import { QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { FullSlug, SimpleSlug, resolveRelative } from "../util/path" +import { QuartzPluginData } from "../plugins/vfile" +import { byDateAndAlphabetical } from "./PageList" +import style from "./styles/links.scss" +import { Date, getDate } from "./Date" +import { GlobalConfiguration } from "../cfg" + +interface Options { + title: string +} + +const defaultOptions = (cfg: GlobalConfiguration): Options => ({ + title: "", +}) + +export default ((userOpts?: Partial) => { + function Links({ allFiles, fileData, displayClass, cfg }: QuartzComponentProps) { + const opts = { ...defaultOptions(cfg), ...userOpts } + return ( + + ) + } + + Links.css = style + return Links +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/index.ts b/quartz/components/index.ts index b3db76be..c4e4635d 100644 --- a/quartz/components/index.ts +++ b/quartz/components/index.ts @@ -19,6 +19,7 @@ import DesktopOnly from "./DesktopOnly" import MobileOnly from "./MobileOnly" import RecentNotes from "./RecentNotes" import Breadcrumbs from "./Breadcrumbs" +import Links from "./Links" export { ArticleTitle, @@ -42,4 +43,5 @@ export { RecentNotes, NotFound, Breadcrumbs, + Links, } diff --git a/quartz/components/styles/links.scss b/quartz/components/styles/links.scss new file mode 100644 index 00000000..e78178d6 --- /dev/null +++ b/quartz/components/styles/links.scss @@ -0,0 +1,19 @@ +.links { + ul { + list-style: none; + margin-top: 1rem; + padding-left: 0; + + & > li { + margin: 1rem 0; + .section > .desc > h3 > a { + background-color: transparent; + } + + .section > .meta { + margin: 0 0 0.5rem 0; + opacity: 0.6; + } + } + } +} From 573e0c6d46eede6ed72bd6645da674ca3f18dce7 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 5 Jan 2024 20:10:41 +0000 Subject: [PATCH 61/94] Quartz sync: Jan 5, 2024, 8:10 PM --- content/notes/auto load zfs keys at boot.md | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 content/notes/auto load zfs keys at boot.md diff --git a/content/notes/auto load zfs keys at boot.md b/content/notes/auto load zfs keys at boot.md new file mode 100644 index 00000000..fc60699a --- /dev/null +++ b/content/notes/auto load zfs keys at boot.md @@ -0,0 +1,34 @@ +--- +date: 2024-01-05 +title: Automatically load zfs keys at boot +description: Creating a systemd service to automatically load zfs keys +tags: + - zfs +--- +I recently setup a new encrypted dataset. Ubuntu/openzfs came with a systemd service to handle mounting it at boot, but not loading the keys - therefore, on reboot, it would fail to mount + +I did some googling and found this: https://github.com/openzfs/zfs/issues/8750#issuecomment-497500144 + +```bash +cat << 'EOF' > /etc/systemd/system/zfs-load-key@.service +[Unit] +Description=Load ZFS keys +DefaultDependencies=no +Before=zfs-mount.service +After=zfs-import.target +Requires=zfs-import.target +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/sbin/zfs load-key %I +[Install] +WantedBy=zfs-mount.service +EOF +``` + +Use like so: +```bash +systemctl enable zfs-load-key@tank-enc +``` + +to, for example, enable key loading for the volume `tank/enc`. It will automatically replace / with - \ No newline at end of file From a8b733cc2c2966708b7381c881a8fc71b64a2539 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 5 Jan 2024 20:46:01 +0000 Subject: [PATCH 62/94] Quartz sync: Jan 5, 2024, 8:46 PM --- content/notes/postgresql.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/notes/postgresql.md b/content/notes/postgresql.md index 9d531f3f..c0a2c48b 100644 --- a/content/notes/postgresql.md +++ b/content/notes/postgresql.md @@ -67,6 +67,9 @@ SELECT * FROM pg_replication_slots; SELECT extract(epoch from now() - pg_last_xact_replay_timestamp()) AS replica_lag ``` - +### Dump database schema only +```bash +pg_dump --schema-only databasename +``` ## Useful tools - [WAL streaming and backup via object storage](https://github.com/wal-g/wal-g) \ No newline at end of file From e3e9455a3d76cccdfc8602f3971f21c6115b55ef Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 5 Jan 2024 21:25:48 +0000 Subject: [PATCH 63/94] Quartz sync: Jan 5, 2024, 9:25 PM --- content/notes/postgres on zfs.md | 1 - content/notes/postgresql.md | 2 +- quartz.config.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/content/notes/postgres on zfs.md b/content/notes/postgres on zfs.md index 4ddd7c2f..28dcc86f 100644 --- a/content/notes/postgres on zfs.md +++ b/content/notes/postgres on zfs.md @@ -6,7 +6,6 @@ tags: title: Running bare metal PostgreSQL on ZFS cover: https://img.ellie.wtf/i/74b97825c93824bf34b2fac6982d43a51ea62914ce9c8f8f7e3459b2ba78cd4b.png --- - I'm setting up new [[postgresql | postgres]] servers for [[atuin | Atuin]]! We're going with a hot replica this time, and making things _much_ more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck. You might also be interested in the [[hetzner k3s]] setup I did for the Atuin api images diff --git a/content/notes/postgresql.md b/content/notes/postgresql.md index c0a2c48b..7867efab 100644 --- a/content/notes/postgresql.md +++ b/content/notes/postgresql.md @@ -5,7 +5,7 @@ title: PostgreSQL tags: - postgresql - infra -updated: 03/01/2024 +updated: 2024/01/05 21:25 --- Some postgres snippets! Just a reference page for the things I forget a lot. diff --git a/quartz.config.ts b/quartz.config.ts index 2680c2cc..69c5281f 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -11,7 +11,7 @@ const config: QuartzConfig = { }, baseUrl: "ellie.wtf", ignorePatterns: ["private", "templates", ".obsidian"], - defaultDateType: "created", + defaultDateType: "modified", theme: { typography: { header: "Schibsted Grotesk", @@ -47,7 +47,7 @@ const config: QuartzConfig = { Plugin.FrontMatter(), Plugin.TableOfContents(), Plugin.CreatedModifiedDate({ - priority: ["frontmatter", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower + priority: ["frontmatter", "git", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower }), Plugin.SyntaxHighlighting(), Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), From 0d1ce6a91fa33b227ca5b1fd1aa893a39fd9b682 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 5 Jan 2024 21:30:16 +0000 Subject: [PATCH 64/94] Quartz sync: Jan 5, 2024, 9:30 PM --- quartz.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz.config.ts b/quartz.config.ts index 69c5281f..e1dc1625 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -11,7 +11,7 @@ const config: QuartzConfig = { }, baseUrl: "ellie.wtf", ignorePatterns: ["private", "templates", ".obsidian"], - defaultDateType: "modified", + defaultDateType: "created", theme: { typography: { header: "Schibsted Grotesk", From 671b0246bed3ff03ecf3c379167b18e130ea51e6 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 5 Jan 2024 22:43:12 +0000 Subject: [PATCH 65/94] Quartz sync: Jan 5, 2024, 10:43 PM --- content/notes/postgresql.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/notes/postgresql.md b/content/notes/postgresql.md index 7867efab..bffd464d 100644 --- a/content/notes/postgresql.md +++ b/content/notes/postgresql.md @@ -16,6 +16,7 @@ Some postgres snippets! Just a reference page for the things I forget a lot. ## Learnings - It's faster to create a table with no index, copy data in, then add indices +- Using `random_page_cost=1.1` for SSD backed databases works much better ## Snippets From 99ee5670c27e270b9db78645764ee19ccf6123f7 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 5 Jan 2024 22:43:45 +0000 Subject: [PATCH 66/94] Quartz sync: Jan 5, 2024, 10:43 PM --- quartz.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz.config.ts b/quartz.config.ts index e1dc1625..2680c2cc 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -47,7 +47,7 @@ const config: QuartzConfig = { Plugin.FrontMatter(), Plugin.TableOfContents(), Plugin.CreatedModifiedDate({ - priority: ["frontmatter", "git", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower + priority: ["frontmatter", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower }), Plugin.SyntaxHighlighting(), Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), From d82465fcd80a26ae5edf35e098c561b983ad7cf1 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 9 Jan 2024 12:11:27 +0000 Subject: [PATCH 67/94] =?UTF-8?q?=F0=9F=98=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ork full time on my open source project.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 content/posts/i quit my job to work full time on my open source project.md diff --git a/content/posts/i quit my job to work full time on my open source project.md b/content/posts/i quit my job to work full time on my open source project.md new file mode 100644 index 00000000..47546783 --- /dev/null +++ b/content/posts/i quit my job to work full time on my open source project.md @@ -0,0 +1,96 @@ +--- +title: I quit my job to work full time on my open source project +description: Quitting my job to start a company +date: 2024-01-09 +cover: https://img.ellie.wtf/i/a820191a338753e81cca394a72f23622f7465fb312f909e3224f8ba5d818348b.jpg +--- + +_Atuin supercharges your productivity by enabling you to rapidly retrieve any command you've ran, at any time, from anywhere. It stores your shell history in a database, recording additional command context and syncing it (e2e encrypted) across devices._ + +The 22nd of December was my last day leading the infrastructure team at [PostHog](https://posthog.com). Going forwards, I'm starting a company and working full time on [Atuin](https://atuin.sh). + +> [!info] +> Atuin will continue to be open source and available for free in its current form as a self-hosted tool. By going full-time I hope I can focus on adding new premium hosted features for advanced users, and begin to support business usage. + +### How did I get here? + +I started Atuin a few years ago now, to scratch an itch I had. I always felt that the shell should be easier to use, and that the state of shell history was the #1 problem there. I'd be googling commands that I knew I'd ran several times at some point in the past, or booting up my other laptop just to retrieve an incantation from earlier that week. + +It turns out, a whole bunch of people felt the same. + +[![Star History Chart](https://api.star-history.com/svg?repos=atuinsh/atuin&type=Date)](https://star-history.com/#atuinsh/atuin&Date) +Some people even contributed - thank you so much to the more-than-150 people who've taken the time to make a PR, no matter how large or small. An extra special thank you to those of you who've stuck around a bit longer ❤️ + +At the beginning of 2023, I spoke at [FOSDEM](https://www.youtube.com/watch?v=uyRmV19qJ2o). I had a _tonne_ of fantastic feedback about the tool, both in person and online. This was great for my motivation - after a couple of years, I was starting to feel tired as an OSS maintainer. + +I started putting more time and energy into the project, which paid off very well. Our usage grew massively - more contributors, more signups, more active community members, more features in blogs/podcasts/etc. We also moved the GitHub repo from `ellie/atuin`, to `atuinsh/atuin`. + +I also had to make some improvements to the infrastructure, mostly because we store a lot of data (encrypted blobs don't compress well...). While it is possible to self host Atuin, many people use the hosted sync server. Some metrics: + +#### User growth +![User growth chart](https://img.ellie.wtf/i/2b7564cc93397c096dcf0ce9f903c5ac06c337f16336b54ddffc4c82e58dd896.jpg) + +#### History growth +![](https://img.ellie.wtf/i/84fb1e13d7fa2e667822803b36fa5d6ab71c0f7ead9e66237ac116a278778371.jpg) + +We started 2023 with users uploading (in total) around 10,000 lines of history a day. We ended it with users uploading almost 200,000 lines a day. + +### A balancing act + +It turns out, the more you put in, the more you get out. While it was great to see growth in the project, it started to get away from me towards the end of the year. I'd work on Atuin before work, but more often than not I wouldn't even get through the open PRs + issues. Let alone work on new features/fixes that were needed. Earlier in the year, my friend [Conrad](https://github.com/conradludgate) had also stepped back from helping out maintaining, which was totally understandable. + +I felt like I was letting the project down, neglecting my social life, and was trying very hard not to be distracted at work. + +In order to grow the project how I'd like to, I needed to be able to dedicate more time to it than is fair while having a full time job. + +So, I'm starting a company and working on Atuin full-time. + +I've always wanted to run my own company, for pretty much my entire life. But I didn't want to start something just for the sake of it. I want to build something people love, and find useful. + +### Money + +I'd been paying out of pocket to run the Atuin servers the whole time. While not a huge amount, it also wasn't tiny - 10s of millions of lines of encrypted shell history add up to a reasonably large amount of storage. Especially when you account for backups and moderate redundancy. + +Towards the end of 2022, a friend suggested I setup GitHub sponsors. I didn't think anything would come of it, but it did! After a few months I received enough sponsorship to cover the server bills, and to offset some of what I'd paid so far. + +I really appreciate all of my sponsors for liking my work enough to contribute financially, when there's no paywall or requirement whatsoever. + +One thing I did notice though - I only really gained sponsors when I regularly mentioned that my sponsors account existed. Otherwise, I lost more to attrition than I gained. This sat weirdly with me, as it felt like begging. + +Speaking privately with some other maintainers, this is not an uncommon experience. I definitely won't be paying my rent with sponsors any time soon. + +I read an [interview with Mike Perham](https://codecodeship.com/blog/2023-04-14-mike-perham), the creator of Sidekiq, that sat with me. I encourage you to check it out, but these two quotes stuck in my mind + +> “in the end, OSS burnout will kill any free project which gets traction” + +I'd certainly felt this. I don't want the project to die, and I know just how great it can be with proper care and attention. But without change, I'd probably give up to spend more time on other projects or pursuits. + +The second quote is one that I'd spend a good while thinking about + +> “If you build something valuable, charge money for it” + +### Looking forwards + +My hope is that I can build valuable features that people wish to pay for, on top of what we already have. Partly advanced/power users, but especially for business use cases. + +I'd love to grow Atuin to the point where I can pass it on, and sponsor the people and projects that we depend on. + +I'm going to wrap up there, but if you have any thoughts please do get in touch! I'd love to hear your thoughts on where Atuin should go next. + +### Sponsors +If you or your company would like to provide support, please check out the [Atuin GitHub sponsors](https://github.com/sponsors/atuinsh)! + +Depending on the tier, I'll mail out stickers and t-shirts to supporters - there's also some tiers for companies that wish to show their support publicly, and receive a link back to their site. + +### Stay in touch + +Website: https://atuin.sh
+Forum: https://forum.atuin.sh
+Email: ellie@atuin.sh + +Discord: https://discord.gg/jR3tfchVvW + +GitHub: https://github.com/atuinsh/atuin + +Mastodon: https://hachyderm.io/@atuin
+Twitter/X: https://twitter.com/atuinsh From 6d65b75baccc69a70a7087b003c8347503df34f7 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 9 Jan 2024 23:32:22 +0000 Subject: [PATCH 68/94] update index --- content/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/index.md b/content/index.md index ebd25352..069835bb 100644 --- a/content/index.md +++ b/content/index.md @@ -48,9 +48,9 @@ Some of my [projects](/projects) have been featured in print, in blogs, and in p ## Work -I currently lead the infrastructure team at [PostHog](https://posthog.com/?ref=ellie.wtf), regularly working with Kubernetes and Terraform. +I'm currently building my open source project [Atuin](https://atuin.sh), which you can read more about [here](https://ellie.wtf/posts/i-quit-my-job-to-work-full-time-on-my-open-source-project). -Previously, I've worked at [Coinbase](https://coinbase.com), [Tracr](https://tracr.com) and [Arachnys](https://arachnys.com). You can stalk my [Linkedin](https://linkedin.com/in/elliehuxtable) if you really want to. +Previously, I lead the infrastructure team at [PostHog](https://posthog.com/?ref=ellie.wtf), worked at [Coinbase](https://coinbase.com), [Tracr](https://tracr.com) and [Arachnys](https://arachnys.com). You can stalk my [Linkedin](https://linkedin.com/in/elliehuxtable) if you really want to. ## Contact Please do get in touch! From c0218d3bf330bfc19ba2fd288323faf2860a55cd Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 15 Jan 2024 15:20:40 +0000 Subject: [PATCH 69/94] add rss tag --- quartz/components/Head.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 42165be9..65d4e047 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -30,6 +30,10 @@ export default (() => { + + {/* I could totally edit the below loop, but I'd rather keep my changes localized to make for easier rebasing on updates */} Date: Mon, 22 Jan 2024 18:32:09 +0000 Subject: [PATCH 70/94] add note --- content/notes/sonoma broken internet.md | 2 ++ content/notes/sqlite extension on macos.md | 32 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 content/notes/sqlite extension on macos.md diff --git a/content/notes/sonoma broken internet.md b/content/notes/sonoma broken internet.md index 6adc7382..c63d6848 100644 --- a/content/notes/sonoma broken internet.md +++ b/content/notes/sonoma broken internet.md @@ -1,6 +1,8 @@ --- title: Upgrading to macOS 14 Sonoma broke my network connection date: 2023-11-27 +tags: + - macos --- I upgraded my mac to macOS 14, Sonoma earlier today. Upon first using it, my internet wouldn't connect! diff --git a/content/notes/sqlite extension on macos.md b/content/notes/sqlite extension on macos.md new file mode 100644 index 00000000..fdbe41b7 --- /dev/null +++ b/content/notes/sqlite extension on macos.md @@ -0,0 +1,32 @@ +--- +title: Using SQLite extensions on macOS +date: 2024-01-22 +tags: + - macos + - sqlite +--- + +I was playing with [sqlite-zstd](https://github.com/phiresky/sqlite-zstd), and upon trying to load the extension... + +``` +.load libsqlite_zstd +Error: unknown command or invalid arguments: "load". Enter ".help" for help +``` + +Sorry? + +Turns out, the sqlite install on macOS is built without the ability to load extensions. + +Luckily, Homebrew has a version that's much more useful + +``` +brew install sqlite +``` + +Note that this is not linked by default. You'll either need to link it + +``` +brew link sqlite --force +``` + +Or, specify the path. \ No newline at end of file From 43210f62c037a8e6cb0ce220944b8f31d599399b Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 23 Jan 2024 09:58:00 +0000 Subject: [PATCH 71/94] hide external icon --- quartz/styles/custom.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss index 48274f6d..b47cb82a 100644 --- a/quartz/styles/custom.scss +++ b/quartz/styles/custom.scss @@ -40,3 +40,7 @@ article p, article li{ } } + +.external-icon { + display: none; +} From b1311fa1db7c501d5042246548394d592aa4519e Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 23 Jan 2024 10:03:28 +0000 Subject: [PATCH 72/94] remove comments --- quartz/components/Footer.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/quartz/components/Footer.tsx b/quartz/components/Footer.tsx index b6cf9eb3..3aba1537 100644 --- a/quartz/components/Footer.tsx +++ b/quartz/components/Footer.tsx @@ -21,8 +21,6 @@ export default ((opts?: Options) => { ))}
- -
) } From aa52c52619b1db57cbcfc28482d81b22276fd16c Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 16 Feb 2024 09:03:57 +0000 Subject: [PATCH 73/94] zsh profiling --- content/notes/profiling zsh.md | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 content/notes/profiling zsh.md diff --git a/content/notes/profiling zsh.md b/content/notes/profiling zsh.md new file mode 100644 index 00000000..a34c7356 --- /dev/null +++ b/content/notes/profiling zsh.md @@ -0,0 +1,41 @@ +--- +title: Profiling zsh and fixing my slow shell +date: 2024-02-16 +tags: + - shell + - zsh +cover: https://img.ellie.wtf/i/65f53ca525e5809faa419fcd628893d92a4d003b3aa1367eda9c3267cd462617.png +--- +My zsh has been a _little_ slow to start for a while. Never enough to be a significant bother, but always something I've been meaning to get around to. Thorsten Ball had a great writeup about this [here](https://registerspill.thorstenball.com/p/how-fast-is-your-shell). Maybe I've had weights in my shoes for too long! + +Recently I've been messing with the `atuin init` command - this is ran at shell startup. I want to ensure it's fast, and we don't cause slow shells for anyone. + +What I did: + +1. Whack `zmodload zsh/zprof` at the top of my `.zshrc` +2. `zprod` at the bottom of the same file + +Upon opening a new shell, I get a nice table showing where zsh is spending time + +![](https://img.ellie.wtf/i/eeadd0c2efb5f70b219c7d6cfba2bad121ea7d17e3459e661a437369f7e68053.png) + +It's pretty obvious that `nvm` is to blame here! With a bit of Googling, it seems like I'm among the last to figure this out. Oops. + +## Fixing it + +I do use nvm reasonably often, so let's fix it + +### Lazy loading +[zsh-lazyload](https://github.com/qoomon/zsh-lazyload) allows you to lazy load commands! That way I only pay the slow nvm penalty when I'm actually using nvm. Not bad. + +Install the plugin as per the readme (I used zplug), and then + +```bash +lazyload nvm -- 'source ~/.nvm/nvm.sh' +``` + +in my .zshrc. Good to go! + +### Replacements + +There are a number of other options nowadays. I've been debating using [asdf](https://asdf-vm.com/) or [mise](https://mise.jdx.dev/), but haven't gotten around to figuring out which works best for me. \ No newline at end of file From 6196c3d226b77629bda529cb4f56a4beb733b2ab Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 16 Feb 2024 09:05:50 +0000 Subject: [PATCH 74/94] typo --- content/notes/profiling zsh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/notes/profiling zsh.md b/content/notes/profiling zsh.md index a34c7356..ca65fac4 100644 --- a/content/notes/profiling zsh.md +++ b/content/notes/profiling zsh.md @@ -13,7 +13,7 @@ Recently I've been messing with the `atuin init` command - this is ran at shell What I did: 1. Whack `zmodload zsh/zprof` at the top of my `.zshrc` -2. `zprod` at the bottom of the same file +2. `zprof` at the bottom of the same file Upon opening a new shell, I get a nice table showing where zsh is spending time From e5781ef2819765a273273907bebd3c0fe5f732f1 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 26 Feb 2024 11:05:30 +0000 Subject: [PATCH 75/94] rg ignore dir --- content/notes/ignore a folder with ripgrep.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 content/notes/ignore a folder with ripgrep.md diff --git a/content/notes/ignore a folder with ripgrep.md b/content/notes/ignore a folder with ripgrep.md new file mode 100644 index 00000000..28728f10 --- /dev/null +++ b/content/notes/ignore a folder with ripgrep.md @@ -0,0 +1,18 @@ +--- +title: Ignore a folder with ripgrep +date: 2024-02-26 +--- +Recently I've had the need to ignore a folder while searching with ripgrep. Normally `rg` will ignore anything specified by `.gitignore`, but I couldn't set that in this case. + +You can specify files or directories to exclude with the `-g` flag, quoting the docs: + +> ``` +> -g, --glob GLOB ... +> ``` +> +> Include or exclude files and directories for searching that match the given glob. This always overrides any other ignore logic. Multiple glob flags may be used. Globbing rules match .gitignore globs. Precede a glob with a ! to exclude it. + +So in my case + +`rg -g '!junk-dir/' search-term` + From 4226f17e78d0a9a3f731a05b9f7d45c7fe92bb32 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 26 Feb 2024 11:06:20 +0000 Subject: [PATCH 76/94] tag --- content/notes/ignore a folder with ripgrep.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/notes/ignore a folder with ripgrep.md b/content/notes/ignore a folder with ripgrep.md index 28728f10..82ada95c 100644 --- a/content/notes/ignore a folder with ripgrep.md +++ b/content/notes/ignore a folder with ripgrep.md @@ -1,6 +1,8 @@ --- title: Ignore a folder with ripgrep date: 2024-02-26 +tags: + - shell --- Recently I've had the need to ignore a folder while searching with ripgrep. Normally `rg` will ignore anything specified by `.gitignore`, but I couldn't set that in this case. From dfd30a8d4ba3a648001294db4bf39decdda4929c Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 9 Apr 2024 12:58:38 +0100 Subject: [PATCH 77/94] notes: typescript path aliases --- content/notes/typescript path aliases.md | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 content/notes/typescript path aliases.md diff --git a/content/notes/typescript path aliases.md b/content/notes/typescript path aliases.md new file mode 100644 index 00000000..2ac8f289 --- /dev/null +++ b/content/notes/typescript path aliases.md @@ -0,0 +1,49 @@ +--- +title: TypeScript import path aliases +date: 2024-04-09 +tags: + - tauri + - typescript +--- +I've been working on some more frontend code recently, and got quite tired of my import paths looking like this + +``` +../../foo/bar/etc +``` + +How horrible! If I ever change directory structure, things are liable to break. That won't do! + +The internet suggested adding the following to my `tsconfig.json` + +```json + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + +``` + +This resolved the squiggly lines in my editor, but my build was still failing. For some context - I'm using Tauri and Vite here. + +It turns out, the issue was due to Vite. In order to support these aliases, we also need to add some config to `vite.config.ts` + +First, install a plugin + +``` +npm install --save-dev vite-tsconfig-paths +``` + +Then, add it to your config + +```typescript +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig(async () => ({ + plugins: [tsconfigPaths()], // I imagine you have other plugins + + // you probably have a bunch more here too +}); +``` + +All done! My import paths are nice now \ No newline at end of file From 1395e151044352f6b23d8c29c3e2466f8e02af0c Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 6 May 2024 16:32:20 +0100 Subject: [PATCH 78/94] block AI crawlers --- content/robots.txt | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 content/robots.txt diff --git a/content/robots.txt b/content/robots.txt new file mode 100644 index 00000000..11d90a8c --- /dev/null +++ b/content/robots.txt @@ -0,0 +1,47 @@ +# I know this can just be _totally_ ignored by crawlers +# But let's hope they behave well :) + +# Code: https://github.com/ellie/notes +# Source: https://darkvisitors.com/ + +# OpenAI, ChatGPT +# https://platform.openai.com/docs/gptbot +User-agent: GPTBot +Disallow: / + +# Google AI (Bard, etc) +# https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers +User-agent: Google-Extended +Disallow: / + +# Block common crawl +# I have mixed feelings on this one, but many models are trained on this data +# It is also used to bootstrap new search indices though +# https://commoncrawl.org/ccbot +User-agent: CCBot +Disallow: / + +# Facebook +# https://developers.facebook.com/docs/sharing/bot/ +User-agent: FacebookBot +Disallow: / + +# Cohere.ai +# https://darkvisitors.com/agents/cohere-ai +User-agent: cohere-ai +Disallow: / + +# Perplexity +# https://docs.perplexity.ai/docs/perplexitybot +User-agent: PerplexityBot +Disallow: / + +# Anthropic +# https://darkvisitors.com/agents/anthropic-ai +User-agent: anthropic-ai +Disallow: / + +# ...also anthropic +# https://darkvisitors.com/agents/claudebot +User-agent: ClaudeBot +Disallow: / From 779f172d626b5b73eb80a607d24362fa2cf57d87 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 6 May 2024 16:41:47 +0100 Subject: [PATCH 79/94] add note --- content/notes/blocking ai crawlers.md | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 content/notes/blocking ai crawlers.md diff --git a/content/notes/blocking ai crawlers.md b/content/notes/blocking ai crawlers.md new file mode 100644 index 00000000..67cc37f5 --- /dev/null +++ b/content/notes/blocking ai crawlers.md @@ -0,0 +1,66 @@ +--- +title: Block AI crawlers +date: 2024-05-06 +tags: + - ai +--- +I have very mixed opinions on LLMs, as they stand. This note won't be digging into my thoughts there - I don't want to have that discussion. + +However, while I'm not exactly doing cutting-edge research here, I do put effort into publishing for _humans_. I like knowing that the 10 minutes I spent writing here might save a few people time in the future. + +I don't do this so someone can build a model, and then sell it for a subscription. + +My new [robots.txt](https://ellie.wtf/robots.txt) now excludes the user agents of a number of known LLM crawlers. Most of the crawlers I've listed are confirmed, and I've cited sources for each. The robots.txt of news publishers is also pretty handy, as they also block LLMs. + +If you'd like to do the same, feel free to take mine! + +``` +# I know this can just be _totally_ ignored by crawlers +# But let's hope they behave well :) + +# Code: https://github.com/ellie/notes +# Source: https://darkvisitors.com/ + +# OpenAI, ChatGPT +# https://platform.openai.com/docs/gptbot +User-agent: GPTBot +Disallow: / + +# Google AI (Bard, etc) +# https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers +User-agent: Google-Extended +Disallow: / + +# Block common crawl +# I have mixed feelings on this one, but many models are trained on this data +# It is also used to bootstrap new search indices though +# https://commoncrawl.org/ccbot +User-agent: CCBot +Disallow: / + +# Facebook +# https://developers.facebook.com/docs/sharing/bot/ +User-agent: FacebookBot +Disallow: / + +# Cohere.ai +# https://darkvisitors.com/agents/cohere-ai +User-agent: cohere-ai +Disallow: / + +# Perplexity +# https://docs.perplexity.ai/docs/perplexitybot +User-agent: PerplexityBot +Disallow: / + +# Anthropic +# https://darkvisitors.com/agents/anthropic-ai +User-agent: anthropic-ai +Disallow: / + +# ...also anthropic +# https://darkvisitors.com/agents/claudebot +User-agent: ClaudeBot +Disallow: / +``` + From c8b832351405d91aeb3fd93477536fefbb831d84 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 14 May 2024 10:35:32 +0700 Subject: [PATCH 80/94] add postgres slow query note --- .../notes/debugging slow postgres queries.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 content/notes/debugging slow postgres queries.md diff --git a/content/notes/debugging slow postgres queries.md b/content/notes/debugging slow postgres queries.md new file mode 100644 index 00000000..666cbf52 --- /dev/null +++ b/content/notes/debugging slow postgres queries.md @@ -0,0 +1,35 @@ +--- +title: Debugging slow PostgreSQL queries +date: 2024-05-14 +tags: + - postgresql +--- + +Earlier today I was trying to figure out why I was getting the occasional latency spike. I suspected I had some slow queries that needed optimising, but in order to check I needed to enable slow query logging. + +If you wish to log all statements that take longer than 100ms with postgres, add this to your config file + +``` +log_min_duration_statement = 100 +``` + +Then reload the postgres config with + +``` +select pg_reload_conf(); +``` + +No restart of the server required! + +Otherwise, the following options can be helpful + +```ini +# Log all statements, not just the slow ones +# This may be very chatty, so make sure your log partition doesn't fill up +log_statement = all/ddl/none +``` + +```ini +# Log durations alongside statements +log_duration = on +``` \ No newline at end of file From 826a7ee7e3fa40f7c27cc132cdc6b80b2dccee4a Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 14 May 2024 10:39:24 +0700 Subject: [PATCH 81/94] add extra bits --- content/notes/debugging slow postgres queries.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/notes/debugging slow postgres queries.md b/content/notes/debugging slow postgres queries.md index 666cbf52..58c2ce7c 100644 --- a/content/notes/debugging slow postgres queries.md +++ b/content/notes/debugging slow postgres queries.md @@ -32,4 +32,8 @@ log_statement = all/ddl/none ```ini # Log durations alongside statements log_duration = on -``` \ No newline at end of file +``` + +In previous jobs, I've found [pganalyze](http://pganalyze.com/) to be fantastic for analysing postgres performance. It does require a few extra setup steps, and is not free. If you're relying on postgres heavily I'd really recommend it! + +Otherwise, my favourite resource for postgres config options is [postgresqlco.nf](https://postgresqlco.nf/) - it's nice for browsing the available options, and handily displays which will require a restart. \ No newline at end of file From 7913bd3076ee58b8f7bc0296218759d631f2f9c2 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 28 May 2024 10:37:39 +0100 Subject: [PATCH 82/94] add git search --- .../notes/search string in any git branch.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 content/notes/search string in any git branch.md diff --git a/content/notes/search string in any git branch.md b/content/notes/search string in any git branch.md new file mode 100644 index 00000000..0908620c --- /dev/null +++ b/content/notes/search string in any git branch.md @@ -0,0 +1,35 @@ +--- +title: Search for a string across all git branches +date: 2024-05-28 +tags: + - git + - shell +--- +I've been working across a few branches lately, and couldn't remember the name of a git branch containing some work I'd done. I did, however, remember the name of an interface I'd defined. + +Seach all git branches for a specific string, with this + +```bash + git rev-list --all | xargs git grep 'SEARCH STRING' +``` + +If a result is found, you'll be given the git SHA + +Get info for the sha + +```bash +git show +``` + +And you'll see something like this + +``` +commit 68f9ecc4d46716f572de943ee90bf236c3065ef4 (HEAD -> ellie/ui-me) +Author: Ellie Huxtable +Date: Sat May 25 09:51:56 2024 +0100 + + wip + +``` + +> [!question] I thought it might also be cool to have [Atuin](https://atuin.sh) track git branches, alongside everything else. Maybe a custom data column? From bb714402cf623ac80e0273d334b0176fad4c0fff Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 28 May 2024 10:39:17 +0100 Subject: [PATCH 83/94] adjust callout --- content/notes/search string in any git branch.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/notes/search string in any git branch.md b/content/notes/search string in any git branch.md index 0908620c..a2ebcc25 100644 --- a/content/notes/search string in any git branch.md +++ b/content/notes/search string in any git branch.md @@ -32,4 +32,5 @@ Date: Sat May 25 09:51:56 2024 +0100 ``` -> [!question] I thought it might also be cool to have [Atuin](https://atuin.sh) track git branches, alongside everything else. Maybe a custom data column? +> [!question] +> I thought it might also be cool to have [Atuin](https://atuin.sh) track git branches, alongside everything else. Maybe a custom data column? From 20bc9262487bda2b9d2541edc470e8ccbc8a842e Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Thu, 13 Jun 2024 23:01:08 +0100 Subject: [PATCH 84/94] add hdzero notes --- content/notes/pavo25 with hdzero.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 content/notes/pavo25 with hdzero.md diff --git a/content/notes/pavo25 with hdzero.md b/content/notes/pavo25 with hdzero.md new file mode 100644 index 00000000..0005a2df --- /dev/null +++ b/content/notes/pavo25 with hdzero.md @@ -0,0 +1,29 @@ +--- +title: HDZero Freestyle V2 VTX with my Pavo25 V2 +date: 2024-06-13 +description: Setting up the HDZero Freestyle V2 with my Pavo25 V2 +--- +Recently I've been playing around with FPV drones - I now have a Pavo25 V2. I had an old set of DJI goggles lying around, but the latency/buffering just didn't work for me. I was set on HDZero. + +Putting the Freestyle V2 in place of the O3 wasn't too difficult, though a bit fiddly. Sharing this in case it can help anyone else. + +The main issue I had was with the connector. Naively, I assumed I'd just be able to plug it in and "it would just work". Nopeee. + +The pins are different! Here are the two pinouts + +### Freestyle VTX + +![](https://img.ellie.wtf/i/2a238f5deed4a871b08ca2de7be8de2b40cfc0eb6be7e1e305f75264fb7088e7.jpg) +### FC pins + +![](https://img.ellie.wtf/i/d54941df0ac9431ffdd24619e56db454d779b079664ac7d7e6eec2bf7e0a9697.jpg) + +So yep. They're the other way around + +If you look at the top of the JST plug for the VTX, you'll see some little legs. Lift them with a knife/tweezers/something, and you can remove the wires. Put them in the correct order! Make sure TX is connected to RX, and vice versa. + +Here's mine + +![](https://img.ellie.wtf/i/c27bda5248244375162b31a144e41810c084bddefe44725313ec31bc779ba405.JPG) + +But yeah, otherwise HDZero is working great with my pavo. OSD fully functions, all good. \ No newline at end of file From 122086d4e748e4d4e7871129d43b74d3c393c95b Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Thu, 13 Jun 2024 23:09:13 +0100 Subject: [PATCH 85/94] tags --- content/notes/pavo25 with hdzero.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/notes/pavo25 with hdzero.md b/content/notes/pavo25 with hdzero.md index 0005a2df..cabaa244 100644 --- a/content/notes/pavo25 with hdzero.md +++ b/content/notes/pavo25 with hdzero.md @@ -2,6 +2,9 @@ title: HDZero Freestyle V2 VTX with my Pavo25 V2 date: 2024-06-13 description: Setting up the HDZero Freestyle V2 with my Pavo25 V2 +tags: + - drone + - fpv --- Recently I've been playing around with FPV drones - I now have a Pavo25 V2. I had an old set of DJI goggles lying around, but the latency/buffering just didn't work for me. I was set on HDZero. From 6a3bb0be34c7ab3605d107bbcc5ad3de1db4163d Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 18 Jun 2024 16:55:44 +0100 Subject: [PATCH 86/94] add week start note --- content/notes/get week start javascript.md | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 content/notes/get week start javascript.md diff --git a/content/notes/get week start javascript.md b/content/notes/get week start javascript.md new file mode 100644 index 00000000..0eb369e9 --- /dev/null +++ b/content/notes/get week start javascript.md @@ -0,0 +1,43 @@ +--- +title: Get the first day of the week with JavaScript +tags: + - javascript + - web +--- +I'm currently building a calendar data display for [Atuin](https://atuin.sh), and wanted to ensure that the week started with the correct day. + +While I'm a big fan of things being configurable and flexible, I don't want to introduce a new config option unless it's required! + +Luckily browser localisation exposes plenty of options. + +## Get a Locale object + +First we need a Locale! They're created like so + +```javascript +new Intl.Locale('CODE') + +// eg + +new Intl.Locale('en-GB') +``` + +We can get the code as follows + +```javascript +new Intl.Locale(navigator.language) +``` + +This is a set by the user in their browser + +## Get the weekinfo + +Once we have the Locale, it's simple + +```javascript +let locale = new Intl.Locale(navigator.language); +let weekinfo = locale.getWeekInfo(); +console.log(weekinfo.firstDay); // prints 1 for me, in the UK +``` + +Docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo \ No newline at end of file From 385d8029a665b13e548b0b3f157e58b6e8739085 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Sun, 23 Jun 2024 09:17:14 +0100 Subject: [PATCH 87/94] add dd --- content/notes/dd show progress.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 content/notes/dd show progress.md diff --git a/content/notes/dd show progress.md b/content/notes/dd show progress.md new file mode 100644 index 00000000..3d6c3961 --- /dev/null +++ b/content/notes/dd show progress.md @@ -0,0 +1,12 @@ +--- +title: Show dd copy progress +date: 2024-06-23 +tags: + - shell +--- + +A super short note, but I forget this all the time. If you'd like to see progress while dd-ing something, add `status=progress`, like so + +```bash +dd if=/input of=/output status=progress +``` \ No newline at end of file From 55e28ed0a742756c028838bc67d6d0c50615b79e Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 26 Jun 2024 16:27:29 +0100 Subject: [PATCH 88/94] add date to note --- content/notes/get week start javascript.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/notes/get week start javascript.md b/content/notes/get week start javascript.md index 0eb369e9..4e297e6d 100644 --- a/content/notes/get week start javascript.md +++ b/content/notes/get week start javascript.md @@ -3,6 +3,7 @@ title: Get the first day of the week with JavaScript tags: - javascript - web +date: 2024-06-18 --- I'm currently building a calendar data display for [Atuin](https://atuin.sh), and wanted to ensure that the week started with the correct day. From a885415e7883820a335731bda2cc87c57b59aac4 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 17 Jul 2024 18:04:36 +0100 Subject: [PATCH 89/94] [2024-07-17] Add new note --- ...custom keybinding with react codemirror.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 content/notes/custom keybinding with react codemirror.md diff --git a/content/notes/custom keybinding with react codemirror.md b/content/notes/custom keybinding with react codemirror.md new file mode 100644 index 00000000..dcf5cf2d --- /dev/null +++ b/content/notes/custom keybinding with react codemirror.md @@ -0,0 +1,40 @@ +--- +title: Custom keybinding with react-codemirror +date: 2024-07-17 +--- + +I'm currently using uiwjs/react-codemirror for a project, and needed to add a custom binding to cmd+enter + +The documentation didn't quite show what I needed, but with the following you can bind custom keys + +```js +`import React from 'react'; +import CodeMirror from '@uiw/react-codemirror'; +import { keymap } from '@codemirror/view'; +import { defaultKeymap } from '@codemirror/commands'; + +const MyCodeEditor = () => { + const handleCmdEnter = React.useCallback((view) => { + console.log('Cmd+Enter pressed!'); + return true; + }, []); + + const customKeymap = keymap.of([ + { + key: 'Mod-Enter', + run: handleCmdEnter, + }, + ]); + + return ( + + ); +}; + +export default MyCodeEditor;` +``` + +Note that you may need to ensure your keymap extension is listed _before_ other extensions, otherwise it may be handled too soon.--- From 48eb90c2ec7030e971956477500a7c58d618b7ac Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Wed, 17 Jul 2024 18:05:56 +0100 Subject: [PATCH 90/94] amend formatting --- content/notes/custom keybinding with react codemirror.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/notes/custom keybinding with react codemirror.md b/content/notes/custom keybinding with react codemirror.md index dcf5cf2d..1eb410c1 100644 --- a/content/notes/custom keybinding with react codemirror.md +++ b/content/notes/custom keybinding with react codemirror.md @@ -8,7 +8,7 @@ I'm currently using uiwjs/react-codemirror for a project, and needed to add a cu The documentation didn't quite show what I needed, but with the following you can bind custom keys ```js -`import React from 'react'; +import React from 'react'; import CodeMirror from '@uiw/react-codemirror'; import { keymap } from '@codemirror/view'; import { defaultKeymap } from '@codemirror/commands'; @@ -34,7 +34,7 @@ const MyCodeEditor = () => { ); }; -export default MyCodeEditor;` +export default MyCodeEditor; ``` Note that you may need to ensure your keymap extension is listed _before_ other extensions, otherwise it may be handled too soon.--- From d11724a5178b95593f850b9fa9efa1ddf453a71d Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 29 Jul 2024 22:47:25 +0100 Subject: [PATCH 91/94] [2024-07-29] Add new note --- content/notes/split git repo.md | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 content/notes/split git repo.md diff --git a/content/notes/split git repo.md b/content/notes/split git repo.md new file mode 100644 index 00000000..ff4eea8e --- /dev/null +++ b/content/notes/split git repo.md @@ -0,0 +1,49 @@ +--- +title: How to split a Git subdirectory into a new repo +description: Splitting a git repo into two, while retaining all history +date: 2024-07-29 +--- +First, install [`git-filter-repo`](https://github.com/newren/git-filter-repo). This is a python script - the required functionality is not built-in to Git. + +It's easy to install. Just drop the script somewhere in your PATH. For me: + +```bash +curl "https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo" -o ~/.local/bin/git-filter-path +``` + + +An important thing to note is that `git-filter-repo` will replace all other files in the repo with those in the subdir. It will not split the subdir into it's own directory. You should operate on a fresh copy of your repo to avoid losing data. + +Let's image our repo looks like the following + +```bash +🦄 ls -l +.rw-r--r-- ellie staff 0 B Mon Jul 29 14:13:39 2024 LICENSE +.rw-r--r-- ellie staff 0 B Mon Jul 29 14:13:34 2024 README.md +drwxr-xr-x ellie staff 64 B Mon Jul 29 14:13:35 2024 src/ +drwxr-xr-x ellie staff 64 B Mon Jul 29 14:13:43 2024 subdir/ +``` + +And we'd like to turn the contents `subdir` into its own repository. + +We would run + +``` +git filter-repo --subdirectory-filter subdir +``` + +## Rewriting commit messages + +I generally follow conventional commits, so I had a bunch of scoped commits like this + +``` +feat(subdir): do a thing +``` + +Now that subdir is its own repo, those commits felt a bit weird. Let's tidy them up! + +``` +git filter-repo --message-callback ' + return re.sub(b"\(subdir\)\n", b"", message, flags=re.MULTILINE) + ' +``` \ No newline at end of file From d2cebd0ac61aa9a3bd939725e60186bc5be256a1 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 29 Jul 2024 22:49:20 +0100 Subject: [PATCH 92/94] [2024-07-29] Update notes --- content/notes/split git repo.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/notes/split git repo.md b/content/notes/split git repo.md index ff4eea8e..6b1ed14f 100644 --- a/content/notes/split git repo.md +++ b/content/notes/split git repo.md @@ -3,7 +3,7 @@ title: How to split a Git subdirectory into a new repo description: Splitting a git repo into two, while retaining all history date: 2024-07-29 --- -First, install [`git-filter-repo`](https://github.com/newren/git-filter-repo). This is a python script - the required functionality is not built-in to Git. +First, install [`git-filter-repo`](https://github.com/newren/git-filter-repo). This is a python script, with no dependencies. It's easy to install. Just drop the script somewhere in your PATH. For me: @@ -12,9 +12,9 @@ curl "https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-r ``` -An important thing to note is that `git-filter-repo` will replace all other files in the repo with those in the subdir. It will not split the subdir into it's own directory. You should operate on a fresh copy of your repo to avoid losing data. +An important thing to note is that `git-filter-repo` will replace all other files in the repo with those in the subdir. You should operate on a fresh copy of your repo to avoid losing data. -Let's image our repo looks like the following +Let's imagine our repo looks like the following ```bash 🦄 ls -l From 9ab7b3ffe68c83aa119e511de3da40aea2fa05a4 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 30 Jul 2024 15:37:18 +0100 Subject: [PATCH 93/94] [2024-07-30] Update notes --- content/notes/git amend author.md | 66 +++++++++++++++++++++++++++++++ content/notes/split git repo.md | 3 ++ 2 files changed, 69 insertions(+) create mode 100644 content/notes/git amend author.md diff --git a/content/notes/git amend author.md b/content/notes/git amend author.md new file mode 100644 index 00000000..230f78bd --- /dev/null +++ b/content/notes/git amend author.md @@ -0,0 +1,66 @@ +--- +title: Amending the author of a Git commit +date: 2024-07-30 +tags: + - git + - shell +--- +It's pretty common that I'll accidentally use the wrong email for a commit. I have a few emails that I like to use for different purposes, so getting it correct is important :) + +### Amend author of last commit + +This one is nice and easy! + +```bash +git commit --amend --author="Example Name " +``` + +### Using interactive rebase + +1. `git rebase -i` on whatever base you want +2. Mark the commits you'd like to change with `edit`, instead of `pick` +3. `git commit --amend --author="Example Name "`, then `git rebase --continue` + +### Using filter branch + +Filter the whole thing! Be careful though, filter-branch can break things if you're not careful + +```bash +git filter-branch -f --commit-filter ' + if [ "$GIT_AUTHOR_EMAIL" = "old@email.com" ]; + then + GIT_AUTHOR_NAME="New Name"; + GIT_AUTHOR_EMAIL="new@email.com"; + git commit-tree "$@"; + else + git commit-tree "$@"; + fi' HEAD +``` + +### Using filter repo +I first mentioned `git-filter-repo` in [[split git repo]], but it's useful here too. Preferable to filter-branch, but not included in the base git install. + +Install with + +```bash +curl "https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo" -o ~/.local/bin/git-filter-path +``` + +Then do either of the following + +#### With mailmap + +If you setup your mailmap to map old -> new, you can run + +```bash +git filter-repo --use-mailmap +``` + +#### Without mailmap +Otherwise, the following works well + +```bash +git filter-repo --email-callback ' + return email if email != b"old@email.com" else b"new@email.com" + ' +``` \ No newline at end of file diff --git a/content/notes/split git repo.md b/content/notes/split git repo.md index 6b1ed14f..2fafbe4d 100644 --- a/content/notes/split git repo.md +++ b/content/notes/split git repo.md @@ -2,6 +2,9 @@ title: How to split a Git subdirectory into a new repo description: Splitting a git repo into two, while retaining all history date: 2024-07-29 +tags: + - shell + - git --- First, install [`git-filter-repo`](https://github.com/newren/git-filter-repo). This is a python script, with no dependencies. From d851310da30076e5db90cbe0370716648c02f606 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:15:18 +0000 Subject: [PATCH 94/94] chore(deps-dev): bump @types/yargs from 17.0.32 to 17.0.33 Bumps [@types/yargs](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/yargs) from 17.0.32 to 17.0.33. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/yargs) --- updated-dependencies: - dependency-name: "@types/yargs" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d2bf386..900110e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.1.1", + "version": "4.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.1.1", + "version": "4.3.0", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", @@ -78,7 +78,7 @@ "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.12", - "@types/yargs": "^17.0.32", + "@types/yargs": "^17.0.33", "esbuild": "^0.19.9", "prettier": "^3.3.3", "tsx": "^4.16.2", @@ -1211,9 +1211,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "dependencies": { "@types/yargs-parser": "*" diff --git a/package.json b/package.json index 3800706d..c2265336 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.12", - "@types/yargs": "^17.0.32", + "@types/yargs": "^17.0.33", "esbuild": "^0.19.9", "prettier": "^3.3.3", "tsx": "^4.16.2",