Compare commits
23 Commits
88439859d9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 58cc6f35a4 | |||
| d4fca30f08 | |||
| 54961ebd1e | |||
| 11cb9f387e | |||
| f359329fa8 | |||
| 360a4af22d | |||
| e1c595f482 | |||
| 1d2fa80c73 | |||
| 4ff168940a | |||
| be99876af5 | |||
| 3f42924242 | |||
| 43d3891bee | |||
| a73cd37980 | |||
| 564c89af7c | |||
| 9f533ffd1a | |||
| 7b1899a010 | |||
| 4c4d048b5c | |||
| 3b30684a5d | |||
| 620011bf79 | |||
| 46e9d9712c | |||
| d6bd7d3359 | |||
| 1fb4ba8109 | |||
| 7177095d03 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1 @@
|
|||||||
public
|
public
|
||||||
resources/_gen
|
|
||||||
.hugo_build.lock
|
|
||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,6 +0,0 @@
|
|||||||
[submodule "themes/ananke"]
|
|
||||||
path = themes/ananke
|
|
||||||
url = https://github.com/theNewDynamic/gohugo-theme-ananke.git
|
|
||||||
[submodule "themes/dark-theme-editor"]
|
|
||||||
path = themes/dark-theme-editor
|
|
||||||
url = https://github.com/JingWangTW/dark-theme-editor.git
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
+++
|
|
||||||
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
|
||||||
date = {{ .Date }}
|
|
||||||
draft = true
|
|
||||||
+++
|
|
||||||
9
assets/wfot.css
Normal file
9
assets/wfot.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.post-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-date {
|
||||||
|
display: block;
|
||||||
|
margin-right: 16px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
54
aws-metric-filters.org
Normal file
54
aws-metric-filters.org
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
#+subtitle:
|
||||||
|
:END:
|
||||||
|
** Structed and passively collected metrics via AWS CloudWatch
|
||||||
|
|
||||||
|
AWS is a vast and sprawling set of services. It can be hard to find the
|
||||||
|
hidden gems like this one so I wanted to point this one out.
|
||||||
|
|
||||||
|
Structured metrics are very helpful to monitoring the health and
|
||||||
|
function of an software system.
|
||||||
|
|
||||||
|
- Do you want to know how long a particular transaction typically takes?
|
||||||
|
- How fast your database queries are?
|
||||||
|
- How long external APIs take to respond?
|
||||||
|
- Fire an alert when a particular function on the site happens too many
|
||||||
|
times? Or too few times?
|
||||||
|
|
||||||
|
...plus a million other things specific to whatever system you're
|
||||||
|
working on.
|
||||||
|
|
||||||
|
There are a lot of great tools for doing this and one that you might not
|
||||||
|
know about is AWS CloudWatch Metric Filters. If you're already on AWS
|
||||||
|
then you should consider these because it requires only that your
|
||||||
|
application logs to CloudWatch.
|
||||||
|
|
||||||
|
If you're on ECS then the [[https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html][awslogs]] log driver for Docker gets you that
|
||||||
|
nearly for free. By "free" I mean that your application itself can
|
||||||
|
have /zero/ dependencies on AWS services and not require any AWS
|
||||||
|
credentials or libraries to start pumping out metrics that you can
|
||||||
|
visualize, alert on and record over time.
|
||||||
|
|
||||||
|
The [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/MonitoringLogData.html][AWS docs]] themselves offer the canonical reference for configuring
|
||||||
|
these so I won't go into detail here.
|
||||||
|
|
||||||
|
However, the gist is that for a log filter you define the following
|
||||||
|
properties
|
||||||
|
|
||||||
|
- A filter pattern for extracting a discrete metric value out of a log
|
||||||
|
entry
|
||||||
|
- A metric name to store the value in
|
||||||
|
- An optional dimension for sub-classifying the value
|
||||||
|
- And finally a log group to extract the metric values from
|
||||||
|
|
||||||
|
After that you just run the application and as the logs roll in the
|
||||||
|
metric values get pumped out. Then you can [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create-alarm-on-metric-math-expression.html][define alarms for alerting]]
|
||||||
|
on them, [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html][graph them]], [[https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#policy-creating-alarm-console][define autoscaling rules]] from them and more.
|
||||||
|
|
||||||
|
To conclude - AWS is big and hairy. While there are benefits to staying
|
||||||
|
platform agnostic, some AWS services don't require much or any coupling
|
||||||
|
of your application code to take advantage of. Cloudwatch Metrics is one
|
||||||
|
of those services and you can get a lot of value out of it with not much
|
||||||
|
effort.
|
||||||
|
|
||||||
15
big-companies.org
Normal file
15
big-companies.org
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Organizing people is a difficult problem which only gets more difficult as youmore people need to be organized.
|
||||||
|
|
||||||
|
The larger a company is the more of its internal structures, rules, policies, history, etc are devoted _just_ to organizing people.
|
||||||
|
|
||||||
|
For me, realizing this was like the first time you hear a flourescent light buzzing in an otherwise quiet room.
|
||||||
|
|
||||||
|
Reasonable people can differ on this point, but for my own sake I'd much rather avoid all the people-organizing baggage that comes with large companies.
|
||||||
|
|
||||||
|
I don't have a hard-and-fast rule about the size of a place I want to work but the larger a place is then generally the more reason I need to want to be there.
|
||||||
|
|
||||||
|
Of course, this is all kind of theoretical at this point, as [[https://flipstone.com][Flipstone]] is my forever home.
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
#+HUGO_BASE_DIR: .
|
|
||||||
* Homelab
|
|
||||||
** HTTPS @ Home
|
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:EXPORT_FILE_NAME: https-at-home
|
#+SETUPFILE: setup.org
|
||||||
:EXPORT_DATE: 2022-11-08
|
#+keywords: homelab
|
||||||
:EXPORT_HUGO_MENU: :menu "main"
|
#+subtitle:
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
|
** HTTPS @ Home
|
||||||
I run a lot of services at home.
|
I run a lot of services at home.
|
||||||
|
|
||||||
This includes, but isn't limited to
|
This includes, but isn't limited to
|
||||||
210
hugo.toml
210
hugo.toml
@@ -1,210 +0,0 @@
|
|||||||
baseURL = 'https://drafts.jamesbrechtel.com/'
|
|
||||||
languageCode = 'en-us'
|
|
||||||
title = 'james brechtel'
|
|
||||||
theme = 'dark-theme-editor'
|
|
||||||
|
|
||||||
# Support Taxonomies
|
|
||||||
[taxonomies]
|
|
||||||
autor = "James brechtel"
|
|
||||||
tag = "tags"
|
|
||||||
category = "categories"
|
|
||||||
_merge = "deep"
|
|
||||||
|
|
||||||
# Theme parameters
|
|
||||||
[params]
|
|
||||||
|
|
||||||
# Parameters applied in HTML <head>
|
|
||||||
[params.site]
|
|
||||||
# Website ICON
|
|
||||||
faviconUrl = ""
|
|
||||||
|
|
||||||
# Do you have any CSS in local? List them in an array.
|
|
||||||
# They should be placed inside "/assets" dir.
|
|
||||||
# Please list your files relative to the /assets directory.
|
|
||||||
# Glob pattern is acceptable
|
|
||||||
# If only a few pages need the extra style files, list them in the page metadata.
|
|
||||||
localCss = []
|
|
||||||
|
|
||||||
# Do you need to add any external CSS? List their URL in an array.
|
|
||||||
# If only a few pages need the extra style files, list them in the page metadata.
|
|
||||||
# These URLs will be inserted into <link> tags directly without any check.
|
|
||||||
externalCss = []
|
|
||||||
|
|
||||||
# Like the one above, but this will download a local copy and combine it with
|
|
||||||
# other CSS files into a single file while in production mode.
|
|
||||||
externalCssDownload = []
|
|
||||||
|
|
||||||
# Do you have any script in local? List them in an array.
|
|
||||||
# They should be placed inside "/assets" dir.
|
|
||||||
# Please list your files relative to the /assets directory.
|
|
||||||
# Glob pattern is acceptable
|
|
||||||
# If only a few pages need the extra script files, list them in the page metadata.
|
|
||||||
localJs = []
|
|
||||||
|
|
||||||
# Do you have any external Script need to add on? List their URL in an array.
|
|
||||||
# If only a few pages need the extra script files, list them in the page metadata.
|
|
||||||
# These URLs will be inserted into <script> tags directly without any check.
|
|
||||||
externalJs = []
|
|
||||||
|
|
||||||
# Like the one above, but this will download a local copy and combine it with
|
|
||||||
# other JS files into a single file while in production mode.
|
|
||||||
externalJsDownload = []
|
|
||||||
|
|
||||||
# The code you could get from Google Search Console.
|
|
||||||
# Please patse the value of content xxx in the following items
|
|
||||||
# <meta name="google-site-verification" content="xxxxxxxxxxxxxxxxxxxxxxxxxx" />
|
|
||||||
googleVerification = ""
|
|
||||||
|
|
||||||
# The code you could get from Microsoft Bing Webmater
|
|
||||||
# Please patse the value of content xxx in the following items
|
|
||||||
# <meta name="msvalidate.01" content="xxxxxxxxxxxxxxxxxxxx" />
|
|
||||||
bingVerifivation = ""
|
|
||||||
|
|
||||||
# Customized info shown in header of the page
|
|
||||||
[params.header]
|
|
||||||
# Website title for header banner.
|
|
||||||
title = "circumlocution"
|
|
||||||
|
|
||||||
# Subtitle for this site, used in homepage only
|
|
||||||
subtitle = "absolutely nothing important"
|
|
||||||
|
|
||||||
# Config about your's site logo, remove this item to hide the logo
|
|
||||||
[params.header.logo]
|
|
||||||
# Where is your site's URL
|
|
||||||
imgUrl = ""
|
|
||||||
|
|
||||||
# If the logo is clickable, where should it be linked?
|
|
||||||
# In default, it will linked to the homepage of the site.
|
|
||||||
logoLink = ""
|
|
||||||
|
|
||||||
# Customized info shown in footer of the page
|
|
||||||
[params.footer]
|
|
||||||
# CopyRight string shown in the footer. Keep it an empty string or remove this item will hide it from the page.
|
|
||||||
copyrightStr = "...."
|
|
||||||
|
|
||||||
# Should show the counter in the footer or not
|
|
||||||
# In the home page, it will show the numbers of all pages
|
|
||||||
# In the sections pages, it will show the numbers of pages within the section
|
|
||||||
# In the taxonomy pages, it will show the numbers of pages belong to the taxonomy.
|
|
||||||
# In the regular content pages, it will show the word count.
|
|
||||||
counter = true
|
|
||||||
|
|
||||||
# Should show the language of the page or not
|
|
||||||
language = true
|
|
||||||
|
|
||||||
# Should show the hugo version or not
|
|
||||||
hugoVersion = true
|
|
||||||
|
|
||||||
# Should show the theme info or not
|
|
||||||
theme = true
|
|
||||||
|
|
||||||
# Should show the edited time of the page or not
|
|
||||||
modifiedTime = true
|
|
||||||
|
|
||||||
# The format of the `modifiedTime`.
|
|
||||||
# Refer to page https://gohugo.io/functions/format/ for more detail.
|
|
||||||
# Below is the default format, please do not remove it, unless you set `false` in `modifiedTime` field.
|
|
||||||
dateFormat = "Jan 02 2006 15:04:05"
|
|
||||||
|
|
||||||
# Should show the current git HEAD hash or not
|
|
||||||
# To make this show up correctly, please follow the prerequisites in page
|
|
||||||
# https://gohugo.io/variables/git/
|
|
||||||
gitHash = true
|
|
||||||
|
|
||||||
# Social link in the footer, listed items are supported, delete unwanted items to hide it.
|
|
||||||
[params.footer.socialLink]
|
|
||||||
github = "jbrechtel"
|
|
||||||
facebook = ""
|
|
||||||
twitter = "8brechtel"
|
|
||||||
email = ""
|
|
||||||
linkedin = ""
|
|
||||||
instagram = "eightbald"
|
|
||||||
telegram = ""
|
|
||||||
medium = ""
|
|
||||||
vimeo = ""
|
|
||||||
youtube = ""
|
|
||||||
|
|
||||||
# Metadata of the site, value will be used in HTML <header>
|
|
||||||
# These value would be used when they didn't appear in the frontmatter of a single page.
|
|
||||||
# In other words, these value will be overwritten by the frontmatter in the single page.
|
|
||||||
[params.globalFrontmatter]
|
|
||||||
# The author of this site. This will be shown in
|
|
||||||
# 1. the footer of all page
|
|
||||||
# 2. the author filed in the single page. (this could be overwritten by the frontmatter of the single page.)
|
|
||||||
# Keep it an empty string or remove this item will hide it from the page
|
|
||||||
author = "James Brechtel"
|
|
||||||
|
|
||||||
# Website description for RSS and SEO. Theme will generate a <meta> tag for this item
|
|
||||||
description = ""
|
|
||||||
|
|
||||||
# Website keywords. Theme will generate a <meta> tag for this item.
|
|
||||||
keywords = "haskell, cycling, hiking, personal, programming"
|
|
||||||
|
|
||||||
# Parameters applied in the homepage only
|
|
||||||
[params.homePage]
|
|
||||||
# Long Descripition shown in home page "Start Block". Is is recommended to have the paragraph shorter than 100 words.
|
|
||||||
siteLongDescription = "This site is a simple outlet to help me clarify my thoughts."
|
|
||||||
|
|
||||||
# If you don't like the title of "siteLongDescription" be "Start" (default),
|
|
||||||
# you may change the value of this item to "Description" or something you like.
|
|
||||||
siteLongDescriptionTitle = "Start"
|
|
||||||
|
|
||||||
# Param to decide whether to show the most recent blog posts or not. (Default: true)
|
|
||||||
showRecentPostsBlock = true
|
|
||||||
|
|
||||||
# Param to decide how many recent posts to show in the home page. (Default: 5)
|
|
||||||
numOfRecentPosts = 10
|
|
||||||
|
|
||||||
# Parameter to decide whether to show the URL behind the title.
|
|
||||||
# It will be more like an editor if it is shown. However, in general, it can be messy if it is displayed.
|
|
||||||
# (Default: true)
|
|
||||||
recentPostShowUrl = true
|
|
||||||
|
|
||||||
|
|
||||||
# Paramater applied in the single page
|
|
||||||
# These values could be overwritten by the frontmatter in the single page.
|
|
||||||
[params.page]
|
|
||||||
# Should include Table of Content in front of the page or not.
|
|
||||||
includeToc = true
|
|
||||||
|
|
||||||
# Should show the author of the page or not.
|
|
||||||
# The author name will be shown in the single page if and only if
|
|
||||||
# 1. this items been set as true and
|
|
||||||
# 2. "aurthor" filed been provided in the
|
|
||||||
# A. single page frontmatter or
|
|
||||||
# B. "author" filed in above "globalFrontmatter" block
|
|
||||||
showAuthor = true
|
|
||||||
|
|
||||||
# Should show the date of the page or not
|
|
||||||
showDate = true
|
|
||||||
|
|
||||||
# The format of the date.
|
|
||||||
# Refer to page https://gohugo.io/functions/format/ for more detail.
|
|
||||||
# Below is the default format, please do not remove it, unless you set `false` in `showDate` field.
|
|
||||||
dateFormat = "2006.01.02"
|
|
||||||
|
|
||||||
# Should show the estimate reading time in front of the page or not.
|
|
||||||
showTimeToRead = true
|
|
||||||
|
|
||||||
# Should show the breadcrumb in front of the page or not.
|
|
||||||
showBreadcrumb = true
|
|
||||||
|
|
||||||
# Should show the "copy" button in the codeblock or not.
|
|
||||||
codeBlockCopible = true
|
|
||||||
|
|
||||||
# Should include LaTeX support on a single page or not?
|
|
||||||
# This parameter will be overwritten by the page front matter (if it has been set).
|
|
||||||
# Since enabling this configuration loads some external JavaScript,
|
|
||||||
# it will slow down the page loading speed.
|
|
||||||
# It's recommended to keep it turned off by default and only enable this configuration when required.
|
|
||||||
useMath = false
|
|
||||||
|
|
||||||
# Filed tp describe how should we pass the "trust" params to the `KaTex` module.
|
|
||||||
# Thos params would affect if we could trust the HTML related code in LaTex blocks.
|
|
||||||
# https://katex.org/docs/options.html
|
|
||||||
trustedmath = false
|
|
||||||
|
|
||||||
[params.preference]
|
|
||||||
# Use "true" to show the real fila name in both breadcrumb and the sidebar
|
|
||||||
# Use "false" to show the tile in the file in both breadcrumb and the sidebar
|
|
||||||
showFileName = false
|
|
||||||
16
index.org
Normal file
16
index.org
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
#+export_file_name: index
|
||||||
|
#+title: circumlocuting
|
||||||
|
#+subtitle:
|
||||||
|
:END:
|
||||||
|
|
||||||
|
* [[file:big-companies][The problem with large organizations]] - 2024-10-02
|
||||||
|
* [[file:simple-css.org][Simple CSS frameworks]] - 2024-09-30
|
||||||
|
* [[file:let-people-fail.org][Let people fail]] - 2024-09-25
|
||||||
|
* TODO [[file:job-description.org][Just what is it you do here?]]
|
||||||
|
* TODO [[file:managing-expectations.org][Managing Expectations]]
|
||||||
|
* [[file:https-at-home.org][HTTPS @ Homelab]]
|
||||||
|
* [[file:multi-room-audio.org][Multi-room audio setup]]
|
||||||
|
* [[file:vi-everywhere.org][vi-editing everywhere]]
|
||||||
|
* [[file:aws-metric-filters.org][AWS Cloudwatch Metric Filters]]
|
||||||
10
job-description.org
Normal file
10
job-description.org
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
:END:
|
||||||
|
** Just what is it you do here?
|
||||||
|
|
||||||
|
I've never liked working at [[file:big-companies.org][larger companies]].
|
||||||
|
|
||||||
|
But that doesn't _always_ simplify things.
|
||||||
|
|
||||||
|
Sometimes at smaller companies you end up defining your own job, to some degree.
|
||||||
29
let-people-fail.org
Normal file
29
let-people-fail.org
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
#+keywords: advice relationships people
|
||||||
|
#+subtitle:
|
||||||
|
:END:
|
||||||
|
|
||||||
|
* How (and why) to let people fail
|
||||||
|
Warning: This, like most things, will involve a fair bit of projection.
|
||||||
|
|
||||||
|
Effective and enjoyable collaboration with other people requires mutual trust.
|
||||||
|
|
||||||
|
I believe that for someone to feel trusted by another person then they need the space to fail.
|
||||||
|
|
||||||
|
I _think_ this is obvious when considering what not having the space to fail looks like.
|
||||||
|
|
||||||
|
Not having the space to fail means your collaborator is doing one of two things:
|
||||||
|
|
||||||
|
1. Directing every action you take a.k.a. micromanaging
|
||||||
|
2. Coming behind you and redoing all of your work
|
||||||
|
|
||||||
|
Both of these are attempts by the other person to minimize risk (or simply cases where they're failing to manage their own anxieties).
|
||||||
|
|
||||||
|
These actions are counter productive to fostering trust and should be avoided unless failure is too costly.
|
||||||
|
|
||||||
|
I'm _not_ saying all collaboration _requires_ building trust. There are times when you simply can't afford failure or mistakes.
|
||||||
|
|
||||||
|
What I am saying is that people frequently misjudge the value in deliberately giving others the space to fail for the sake of fostering trust.
|
||||||
|
|
||||||
|
Building trust is important and we should do it deliberately.
|
||||||
5
managing-expectations.org
Normal file
5
managing-expectations.org
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
:END:
|
||||||
|
|
||||||
|
* Managing expectations!
|
||||||
46
multi-room-audio.org
Normal file
46
multi-room-audio.org
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
#+keywords: homelab snapcast audio
|
||||||
|
#+subtitle:
|
||||||
|
:END:
|
||||||
|
** My multiroom audio setup
|
||||||
|
|
||||||
|
I've put my home audio solution together out of the following
|
||||||
|
components.
|
||||||
|
|
||||||
|
- [[https://github.com/badaix/snapcast][Snapcast]]
|
||||||
|
|
||||||
|
- [[https://www.musicpd.org/][MPD]]
|
||||||
|
|
||||||
|
- [[https://github.com/librespot-org/librespot][Librespot]]
|
||||||
|
|
||||||
|
- [[https://github.com/mikebrady/shairport-sync][Shairport-sync]]
|
||||||
|
|
||||||
|
- A mini-PC in my closet running the above software
|
||||||
|
|
||||||
|
- Two Raspberry Pi 4s
|
||||||
|
|
||||||
|
- Four Raspberry Pi Zero Ws
|
||||||
|
|
||||||
|
- Some desktop speakers and some Bluetooth speakers (wired to the Pis)
|
||||||
|
|
||||||
|
Each of the Raspberry Pis is in a room or porch attached to a speaker.
|
||||||
|
|
||||||
|
Snapcast lets me take an audio source and synchronize it across multiple
|
||||||
|
clients. Each of the Raspberry Pis are running a =snapclient= instance
|
||||||
|
and play whatever the =snapserver= instance tells them to.
|
||||||
|
|
||||||
|
Snapcast is setup to send whichever of the streams (MPD, Spotify,
|
||||||
|
Shairport-sync/AirPlay) is playing audio to each of the clients that are
|
||||||
|
connected to it.
|
||||||
|
|
||||||
|
This lets me or anyone else on my WiFi network play directly on one or
|
||||||
|
more of the speakers - each named for the room that they're in using
|
||||||
|
either Spotify, AirPlay, picking from my own music collection or by
|
||||||
|
pointing at a URL (like to a podcast episode).
|
||||||
|
|
||||||
|
This works out great and we've used it at home for the past year.
|
||||||
|
|
||||||
|
I'd like to get the podcast experience to a more seamless place but it's
|
||||||
|
pretty OK right now using AirMusic on my phone to play audio to the
|
||||||
|
speakers over AirPlay.
|
||||||
519
one.org
Normal file
519
one.org
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
* You're being willfully obtuse
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default-home-list-pages
|
||||||
|
:CUSTOM_ID: /
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Here's what I'm thinking...
|
||||||
|
|
||||||
|
* Multi-room audio setup
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2022-11-08
|
||||||
|
:CUSTOM_ID: /multi-room-audio/
|
||||||
|
:END:
|
||||||
|
I've put my home audio solution together out of the following
|
||||||
|
components.
|
||||||
|
|
||||||
|
- [[https://github.com/badaix/snapcast][Snapcast]]
|
||||||
|
|
||||||
|
- [[https://www.musicpd.org/][MPD]]
|
||||||
|
|
||||||
|
- [[https://github.com/librespot-org/librespot][Librespot]]
|
||||||
|
|
||||||
|
- [[https://github.com/mikebrady/shairport-sync][Shairport-sync]]
|
||||||
|
|
||||||
|
- A mini-PC in my closet running the above software
|
||||||
|
|
||||||
|
- Two Raspberry Pi 4s
|
||||||
|
|
||||||
|
- Four Raspberry Pi Zero Ws
|
||||||
|
|
||||||
|
- Some desktop speakers and some Bluetooth speakers (wired to the Pis)
|
||||||
|
|
||||||
|
Each of the Raspberry Pis is in a room or porch attached to a speaker.
|
||||||
|
|
||||||
|
Snapcast lets me take an audio source and synchronize it across multiple
|
||||||
|
clients. Each of the Raspberry Pis are running a =snapclient= instance
|
||||||
|
and play whatever the =snapserver= instance tells them to.
|
||||||
|
|
||||||
|
Snapcast is setup to send whichever of the streams (MPD, Spotify,
|
||||||
|
Shairport-sync/AirPlay) is playing audio to each of the clients that are
|
||||||
|
connected to it.
|
||||||
|
|
||||||
|
This lets me or anyone else on my WiFi network play directly on one or
|
||||||
|
more of the speakers - each named for the room that they're in using
|
||||||
|
either Spotify, AirPlay, picking from my own music collection or by
|
||||||
|
pointing at a URL (like to a podcast episode).
|
||||||
|
|
||||||
|
This works out great and we've used it at home for the past year.
|
||||||
|
|
||||||
|
I'd like to get the podcast experience to a more seamless place but it's
|
||||||
|
pretty OK right now using AirMusic on my phone to play audio to the
|
||||||
|
speakers over AirPlay.
|
||||||
|
|
||||||
|
* HTTPS @ Homelab
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2022-11-08
|
||||||
|
:CUSTOM_ID: /large-companies/
|
||||||
|
:END:
|
||||||
|
I run a lot of services at home.
|
||||||
|
|
||||||
|
This includes, but isn't limited to
|
||||||
|
|
||||||
|
- [[https://archivebox.io/][ArchiveBox]]
|
||||||
|
- [[https://github.com/dani-garcia/vaultwarden][VaultWarden]]
|
||||||
|
- [[https://github.com/navidrome/navidrome][Navidrome]]
|
||||||
|
- [[https://plex.tv][Plex]]
|
||||||
|
- [[https://github.com/LibrePhotos/librephotos][LibrePhotos]]
|
||||||
|
- This blog
|
||||||
|
|
||||||
|
and a lot more.
|
||||||
|
|
||||||
|
Pretty much anything that's served up over HTTP is always nice if not
|
||||||
|
necessary to have behind TLS.
|
||||||
|
|
||||||
|
[[https://letsencrypt.org/][LetsEncrypt]] long ago brought free certs to
|
||||||
|
the masses and there are a lot of tools for automating that nowadays.
|
||||||
|
|
||||||
|
My preferred approach for getting all the unnecessary nonsense I
|
||||||
|
self-host at home behind TLS is [[https://caddyserver.com][Caddy]].
|
||||||
|
|
||||||
|
I have a super straight forward setup, generally:
|
||||||
|
|
||||||
|
- Run Caddy in a docker container
|
||||||
|
- Create a wildcard CNAME record in my DNS pointing at my home's
|
||||||
|
(effectively) static IP
|
||||||
|
- Add an entry in my Caddyfile for each services I'm running at home on
|
||||||
|
its own subdomain
|
||||||
|
- If it's a service then I add it with a =reverse_proxy= block
|
||||||
|
- If it's a static site (like this) then there's a block for
|
||||||
|
- If it's something I want only accessible on my home network then I put
|
||||||
|
a block like
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
@local_network {
|
||||||
|
path *
|
||||||
|
remote_ip
|
||||||
|
}
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
in the directive. And voila.
|
||||||
|
|
||||||
|
Then tell Caddy to reload the config and I'm done.
|
||||||
|
* AWS Cloudwatch Metric Filters
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2022-11-12
|
||||||
|
:CUSTOM_ID: /large-companies/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
** Structed and passively collected metrics via AWS CloudWatch
|
||||||
|
|
||||||
|
AWS is a vast and sprawling set of services. It can be hard to find the
|
||||||
|
hidden gems like this one so I wanted to point this one out.
|
||||||
|
|
||||||
|
Structured metrics are very helpful to monitoring the health and
|
||||||
|
function of an software system.
|
||||||
|
|
||||||
|
- Do you want to know how long a particular transaction typically takes?
|
||||||
|
- How fast your database queries are?
|
||||||
|
- How long external APIs take to respond?
|
||||||
|
- Fire an alert when a particular function on the site happens too many
|
||||||
|
times? Or too few times?
|
||||||
|
|
||||||
|
...plus a million other things specific to whatever system you're
|
||||||
|
working on.
|
||||||
|
|
||||||
|
There are a lot of great tools for doing this and one that you might not
|
||||||
|
know about is AWS CloudWatch Metric Filters. If you're already on AWS
|
||||||
|
then you should consider these because it requires only that your
|
||||||
|
application logs to CloudWatch.
|
||||||
|
|
||||||
|
If you're on ECS then the [[https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html][awslogs]] log driver for Docker gets you that
|
||||||
|
nearly for free. By "free" I mean that your application itself can
|
||||||
|
have /zero/ dependencies on AWS services and not require any AWS
|
||||||
|
credentials or libraries to start pumping out metrics that you can
|
||||||
|
visualize, alert on and record over time.
|
||||||
|
|
||||||
|
The [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/MonitoringLogData.html][AWS docs]] themselves offer the canonical reference for configuring
|
||||||
|
these so I won't go into detail here.
|
||||||
|
|
||||||
|
However, the gist is that for a log filter you define the following
|
||||||
|
properties
|
||||||
|
|
||||||
|
- A filter pattern for extracting a discrete metric value out of a log
|
||||||
|
entry
|
||||||
|
- A metric name to store the value in
|
||||||
|
- An optional dimension for sub-classifying the value
|
||||||
|
- And finally a log group to extract the metric values from
|
||||||
|
|
||||||
|
After that you just run the application and as the logs roll in the
|
||||||
|
metric values get pumped out. Then you can [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create-alarm-on-metric-math-expression.html][define alarms for alerting]]
|
||||||
|
on them, [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html][graph them]], [[https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#policy-creating-alarm-console][define autoscaling rules]] from them and more.
|
||||||
|
|
||||||
|
To conclude - AWS is big and hairy. While there are benefits to staying
|
||||||
|
platform agnostic, some AWS services don't require much or any coupling
|
||||||
|
of your application code to take advantage of. Cloudwatch Metrics is one
|
||||||
|
of those services and you can get a lot of value out of it with not much
|
||||||
|
effort.
|
||||||
|
|
||||||
|
* vi-editing everywhere
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2022-11-13
|
||||||
|
:CUSTOM_ID: /vi-everywhere/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
For my sake, I prefer to have Vim bindings in as many places as
|
||||||
|
possible.
|
||||||
|
|
||||||
|
Most shells can be configured to use Vim bindings by putting =set -o vi=
|
||||||
|
somewhere in your shell startup script.
|
||||||
|
|
||||||
|
If you're using ZSH then you'll probably want an additional binding to
|
||||||
|
restore CTRL-R reverse history search.
|
||||||
|
|
||||||
|
=bindkey '^R' history-incremental-search-backward=
|
||||||
|
|
||||||
|
For CLI tools that use the =readline= library then you can configure its
|
||||||
|
input mode using a =.inputrc= file in your =$HOME= directory.
|
||||||
|
|
||||||
|
This affects REPLs like =ghci= and tools like =psql=.
|
||||||
|
|
||||||
|
#+begin_src txt
|
||||||
|
set editing-mode vi
|
||||||
|
$if mode=vi
|
||||||
|
|
||||||
|
set keymap vi-command
|
||||||
|
# these are for vi-command mode
|
||||||
|
Control-l: clear-screen
|
||||||
|
|
||||||
|
set keymap vi-insert
|
||||||
|
# these are for vi-insert mode
|
||||||
|
Control-l: clear-screen
|
||||||
|
$endif
|
||||||
|
#+end_src
|
||||||
|
* The problem with large companies
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-09-20
|
||||||
|
:CUSTOM_ID: /large-companies/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Organizing people is a difficult problem which only gets more difficult as youmore people need to be organized.
|
||||||
|
|
||||||
|
The larger a company is the more of its internal structures, rules, policies, history, etc are devoted _just_ to organizing people.
|
||||||
|
|
||||||
|
For me, realizing this was like the first time you hear a flourescent light buzzing in an otherwise quiet room.
|
||||||
|
|
||||||
|
Reasonable people can differ on this point, but for my own sake I'd much rather avoid all the people-organizing baggage that comes with large companies.
|
||||||
|
|
||||||
|
I don't have a hard-and-fast rule about the size of a place I want to work but the larger a place is then generally the more reason I need to want to be there.
|
||||||
|
|
||||||
|
Of course, this is all kind of theoretical at this point, as [[https://flipstone.com][Flipstone]] is my forever home.
|
||||||
|
|
||||||
|
* Simple CSS frameworks
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-09-29
|
||||||
|
:CUSTOM_ID: /simple-css-frameworks/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
I really like simple drop-in CSS resets like the one I use for this site.
|
||||||
|
|
||||||
|
At the time of writing, I'm using [[https://picocss.com/][Pico]] but I also considered [[https://yegor256.github.io/tacit/][tacit]]
|
||||||
|
|
||||||
|
The idea is that they provide nice default styling of HTML elements out of the box without the need to reference any specific classes.
|
||||||
|
|
||||||
|
The idea works well for sites that are much more content than layout - like this one.
|
||||||
|
|
||||||
|
Using tacit is a matter of incluing this link tag in the page's HEAD element:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||||
|
#+END_SRC
|
||||||
|
* Let people fail
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-10-02
|
||||||
|
:CUSTOM_ID: /let-people-fail/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Warning: This, like most things, will involve a fair bit of projection.
|
||||||
|
|
||||||
|
Effective and enjoyable collaboration with other people requires mutual trust.
|
||||||
|
|
||||||
|
I believe that for someone to feel trusted by another person then they need the space to fail.
|
||||||
|
|
||||||
|
I _think_ this is obvious when considering what not having the space to fail looks like.
|
||||||
|
|
||||||
|
Not having the space to fail means your collaborator is doing one of two things:
|
||||||
|
|
||||||
|
1. Directing every action you take a.k.a. micromanaging
|
||||||
|
2. Coming behind you and redoing all of your work
|
||||||
|
|
||||||
|
Both of these are attempts by the other person to minimize risk (or simply cases where they're failing to manage their own anxieties).
|
||||||
|
|
||||||
|
These actions are counter productive to fostering trust and should be avoided unless failure is too costly.
|
||||||
|
|
||||||
|
I'm _not_ saying all collaboration _requires_ building trust. There are times when you simply can't afford failure or mistakes.
|
||||||
|
|
||||||
|
What I am saying is that people frequently misjudge the value in deliberately giving others the space to fail for the sake of fostering trust.
|
||||||
|
|
||||||
|
Building trust is important and we should do it deliberately.
|
||||||
|
* TODO Managing Expectations
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-10-04
|
||||||
|
:CUSTOM_ID: /managing-expectations/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
:DRAFT:
|
||||||
|
|
||||||
|
I'll figure this out one day. Until then I'll just keep saying yes and burning myself out making everyone happy.
|
||||||
|
:END:
|
||||||
|
|
||||||
|
* TODO Tools I love
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-10-07
|
||||||
|
:CUSTOM_ID: /tools-i-love/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
I work on a computer all day. I do things from query databases, making
|
||||||
|
a lot of HTTP API calls, parsing, transforming and generally munging
|
||||||
|
structured and unstructured data of all kinds. I edit and compile
|
||||||
|
code. I remote into servers and automate a lot of my daily tasks. I
|
||||||
|
take, organize, search and refine notes.
|
||||||
|
|
||||||
|
I prefer terminal-based tools because I can use them on any of my
|
||||||
|
machines remotely without much hassle. I also find that I'm just more
|
||||||
|
efficient with text-input and keyboard driven interactions, especially
|
||||||
|
if modal editing is available.
|
||||||
|
|
||||||
|
Fortunately for me we're the golden age of terminal-based tools, IMHO.
|
||||||
|
|
||||||
|
Anyway, with all that said, I present the long list of tools I use daily and why I like them:
|
||||||
|
|
||||||
|
** Arch Linux
|
||||||
|
I won't go into too much detail here. Rolling release, Arch Wiki and
|
||||||
|
AUR. I run Arch with ZFS on 4 machines. My desktop install is over 10
|
||||||
|
years old at this point. I rarely encounter any sort of bleeding edge
|
||||||
|
breakage that people seem to think is common with Arch.
|
||||||
|
** KDE Plasma
|
||||||
|
It has just enough shortcuts for window management that I can do 90%
|
||||||
|
of what I would do with tiling WMs like i3 and Sway and I prefer not
|
||||||
|
having to spend time configuring one of the various i3bar-etc
|
||||||
|
applications to get the system tray and all that working right.
|
||||||
|
|
||||||
|
KDE just works, it looks nice and lets me configure it as much as I want.
|
||||||
|
|
||||||
|
I do find myself WM/DE-curious and I try other things from time to
|
||||||
|
time. [[https://github.com/paperwm/PaperWM][PaperWM]] is on my short list to try out.
|
||||||
|
|
||||||
|
** Emacs
|
||||||
|
Won't elaborate here much either - I like Emacs mostly because of Evil
|
||||||
|
mode and Org mode but there is a long-tail of other packages and
|
||||||
|
configurability that also make me prefer it over Vim or Neovim.
|
||||||
|
|
||||||
|
I still use Neovim for quick one-off edits from time to time but 90%
|
||||||
|
of my day is spent in Emacs.
|
||||||
|
|
||||||
|
I think newer editors like Helix are interesting, but without full Org
|
||||||
|
mode support I couldn't imagine switching.
|
||||||
|
|
||||||
|
** Nushell
|
||||||
|
[[https://www.nushell.sh/][Nushell]] is a modern shell written in Rust with first class support for
|
||||||
|
structured data, syntax and semantics that draw a nice balance between
|
||||||
|
reasonable programming language and an ergonomic shell UX.
|
||||||
|
|
||||||
|
I find myself being willing to automate more complex tasks using
|
||||||
|
Nushell than I would with Z shell or Bash.
|
||||||
|
|
||||||
|
** VisiData
|
||||||
|
[[https://www.visidata.org/][VisiData]] is a nice terminal spreadsheet and CSV tool. I don't have to
|
||||||
|
_create_ a lot of spreadsheets but I do have to find data in them and
|
||||||
|
CSVs often enough, for work, that having something that fits my normal
|
||||||
|
workflow here is really nice.
|
||||||
|
|
||||||
|
While I don't think VisiData will write to XLSX files, it will let you
|
||||||
|
edit sheets and save them as CSV or TSV.
|
||||||
|
|
||||||
|
This tool was the initial motivation for writing this post.
|
||||||
|
|
||||||
|
** Konsole
|
||||||
|
Nothing super interesting to say about it. It just works.
|
||||||
|
|
||||||
|
All the modern terminals are nice, I like WezTerm, Alacritty and
|
||||||
|
Kitty. I switch between them for one reason or another at different
|
||||||
|
times, but I currently have Konsole bound to my terminal shortcut in
|
||||||
|
KDE so it's my terminal of choice.
|
||||||
|
** TODO Unison
|
||||||
|
** Firefox
|
||||||
|
There's not a whole lot, I think, that differentiates the browsers from one another these days
|
||||||
|
** Chrome Emacs
|
||||||
|
[[https://addons.mozilla.org/en-US/firefox/addon/chrome-emacs/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search][Chrome Emacs]] is both a Chrome and Firefox extension that lets you
|
||||||
|
pretty seamlessly edit text areas in your browser from within
|
||||||
|
Emacs. Since I find myself [[#/job-description/][writing a lot at work]] these days, it's nice
|
||||||
|
to be able to edit story details in [[https://www.shorcut.com][Shortcut]] from Emacs with nice
|
||||||
|
modal editing.
|
||||||
|
|
||||||
|
** TODO Syncthing
|
||||||
|
* TODO Just what is it you do here?
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-10-08
|
||||||
|
:CUSTOM_ID: /job-description/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
I've never liked working at [[#/large-companies/][large companies]]. Mostly because I think
|
||||||
|
they complicate things, but some things are more complicated at small
|
||||||
|
companies.
|
||||||
|
|
||||||
|
Specifically, leadership roles tend to be fluid in their definition
|
||||||
|
where they grow organically over time. Also, as I'm learning, old
|
||||||
|
responsibilities don't tend to get pruned on their own. So you have to
|
||||||
|
be diligent about shaping your role over time or you'll end up
|
||||||
|
over-burdened and unable to do a good job at any of your
|
||||||
|
responsibilities.
|
||||||
|
|
||||||
|
My formal job title is (I think) "Director of Engineering"
|
||||||
|
|
||||||
|
My reponsibilities include
|
||||||
|
|
||||||
|
**** Software design
|
||||||
|
I don't design all of the software here by any means, but I am either
|
||||||
|
doing the design myself or I'm involved in the design conversation of nearly
|
||||||
|
any non-trivial component.
|
||||||
|
|
||||||
|
We're starting to outgrow this but it's proving a little difficult for
|
||||||
|
both me to let go of being involved in everything and for others to
|
||||||
|
let me be less involved.
|
||||||
|
|
||||||
|
**** Keeping production from breaking
|
||||||
|
At the end of the day, if the system is on fire then I have to make
|
||||||
|
sure it gets fixed.
|
||||||
|
|
||||||
|
Fortunately, our system is fairly resilient and we have a rotating
|
||||||
|
"support" role that everyone gets a turn at - so I am not personally
|
||||||
|
responding to every issue that comes up.
|
||||||
|
|
||||||
|
However, if we do have a big enough or hard enough problem then I need
|
||||||
|
to be able to provide support. And that usually means the situation is
|
||||||
|
urgent so I have to know the details of the system well enough to
|
||||||
|
resolve issues quickly.
|
||||||
|
|
||||||
|
Truly, though, this responsibility is a long-view one - I need to
|
||||||
|
ensure that the software we're building is not falling over on
|
||||||
|
itself. Thankfully, our tech stack is faily reliable compared
|
||||||
|
to many others I've used in companies prior.
|
||||||
|
|
||||||
|
**** Business goal prioritization
|
||||||
|
Providing technical input to and vetting of business goals.
|
||||||
|
|
||||||
|
This is basically a combination of saying
|
||||||
|
- "X will take (days|weeks|months|years)"
|
||||||
|
- "Y will be (easier|better|less risky) if we do it after X"
|
||||||
|
- "We can get 90% of the benefits of A if we do B - at half the cost - instead"
|
||||||
|
- etc...
|
||||||
|
|
||||||
|
**** Writing stories and technical plans
|
||||||
|
Many (but not all) of our "tickets" "cards" "stories" what-have-you
|
||||||
|
end up getting written directly by me. This is, in some sense, an easy
|
||||||
|
job to delegate out but it's risky to do so because getting this part
|
||||||
|
wrong can lead to a lot of re-work amongst other costs.
|
||||||
|
|
||||||
|
**** Iteration planning
|
||||||
|
Deciding what the team will actually do in a given week.
|
||||||
|
|
||||||
|
**** Ensuring timelines get met
|
||||||
|
While we don't have a lot of "hard dates" on deliverables compared to
|
||||||
|
[[#/large-companies/][large companies]], we have them sometimes and it's my responsibility to
|
||||||
|
either ensure we hit them or to understand why we didn't so we can
|
||||||
|
better [[#/managing-expectations][manage expectations]] in the future.
|
||||||
|
|
||||||
|
**** Discuss story details, expectations, changes, etc
|
||||||
|
[[https://en.wikipedia.org/wiki/User_story][A user story is a promise for a conversation]]. Very often I am the one
|
||||||
|
keeping that promise and this puts me in the middle of a lot of
|
||||||
|
conversations.
|
||||||
|
|
||||||
|
**** Adjudicate technical disagreements
|
||||||
|
Fortunately this doesn't happen all that often - and usually when it
|
||||||
|
does it's around more trivial things (bikeshedding affects us all),
|
||||||
|
but it does happen.
|
||||||
|
**** Maintaining technical quality
|
||||||
|
When we have code quality issues I feel personally responsible.
|
||||||
|
|
||||||
|
It's my job to either prevent them in the first place, or plan an
|
||||||
|
execute work to alleviate quality issues.
|
||||||
|
|
||||||
|
Balancing that work with business deliverables is a skill unto itself.
|
||||||
|
|
||||||
|
**** Primary interrupt
|
||||||
|
This responsiblity is a hold-over from my tech lead days and it's one
|
||||||
|
I need to get rid of.
|
||||||
|
|
||||||
|
Being the primary interrupt means, to me, protecting the team from
|
||||||
|
interruptions and allowing them to focus on executing the current plan
|
||||||
|
of work (i.e. the stories in the iteration, our current deliverable or
|
||||||
|
goal otherwise).
|
||||||
|
|
||||||
|
In practice this means it's hard for me to focus.
|
||||||
|
|
||||||
|
We've made some changes to the support structure this past year that
|
||||||
|
have helped with this immensely.
|
||||||
|
|
||||||
|
However, at the same time we've grown the development team so now my
|
||||||
|
other responsibility of feeding the machine (i.e. writing stories) has
|
||||||
|
chipped away at some of the focus gains I've made.
|
||||||
|
|
||||||
|
* Finally, Emacs
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-10-15
|
||||||
|
:CUSTOM_ID: /emacs/
|
||||||
|
:END:
|
||||||
|
|
||||||
|
I've been thinking a lot about [[#/tools-i-love][tooling]] lately. Being a programmer at heart my most important tool is, of course, my editor.
|
||||||
|
|
||||||
|
I have been promiscuous in my use of editors in the past, ranging from my [[https://www.ultraedit.com/][first love]] back from my heathen days on Windows to my forever home: [[https://www.gnu.org/software/emacs/][Emacs]].
|
||||||
|
|
||||||
|
I've been using Emacs for a few years at this point, but I don't think I really internalized that this is where I _want_ to stay until just recently.
|
||||||
|
|
||||||
|
While reading through the OrgMode FAQ one evening, as one does, I found myself looking at a link to a video about Emacs from 2008.
|
||||||
|
|
||||||
|
"2008!" I thought to myself. WTF was I doing in 2008 wasting my time with Resharper (or some such nonsense, probably).
|
||||||
|
|
||||||
|
If only I'd been mastering Org Mode to, well, organize my life! How far would I in my Emacs journey by now?
|
||||||
|
|
||||||
|
I refuse to spend much time on that actual line of thinking, tempting as it is to self-flagellate, I do think it indicates a shift in perspective.
|
||||||
|
|
||||||
|
I'm in this for the long haul. I'm reminded of advice from [[http://davidvollbracht.com/][smartest person I know]] about editors: (paraphrased, because I wasn't diligent about note taking when he said it!) "Your text editor is a use-it-for-life tool. You should pick one worthy of such an investment in your time."
|
||||||
|
|
||||||
|
In his view there were only two such editors at the time: Vim and Emacs.
|
||||||
|
|
||||||
|
He was already well on his way down the Vim road and I spent several years following along.
|
||||||
|
|
||||||
|
As a result, I found ~Jesus~ modal editing and I [[#/vi-everywhere][haven't looked back]].
|
||||||
|
|
||||||
|
Anyway, none of this is meant to be rhetorical in any way...more just a description of my own journey.
|
||||||
|
|
||||||
|
I won't elaborate on my specific usage of Emacs and Org Mode but suffice to say I'm getting a lot of benefit out of both tools lately and I only see more value in them down the road.
|
||||||
|
|
||||||
|
I know this kind of post is essentially a death knell for a personal blog. I'll see you again in 2028, I guess.
|
||||||
|
|
||||||
|
* TODO Story writing
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-11-05
|
||||||
|
:CUSTOM_ID: story-writing
|
||||||
|
:END:
|
||||||
|
|
||||||
|
* TODO Election 2024
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: wfot-default
|
||||||
|
:DATE: 2024-11-05
|
||||||
|
:CUSTOM_ID: election-2024
|
||||||
|
:END:
|
||||||
221
onerc.el
Normal file
221
onerc.el
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
(setq wfot-styles
|
||||||
|
'((mvp . "https://unpkg.com/mvp.css")
|
||||||
|
(bahunya . "https://cdn.jsdelivr.net/gh/kimeiga/bahunya/dist/bahunya.min.css")
|
||||||
|
(awsm . "https://unpkg.com/awsm.css/dist/awsm.min.css")
|
||||||
|
(holiday . "https://cdn.jsdelivr.net/npm/holiday.css@0.11.2")
|
||||||
|
(tacit . "https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css")
|
||||||
|
(writ . "//writ.cmcenroe.me/1.0.4/writ.min.css")
|
||||||
|
(simple . "https://cdn.simplecss.org/simple.min.css")
|
||||||
|
(pico-amber . "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.amber.min.css")
|
||||||
|
(pico-fluid . "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.fluid.classless.min.css")
|
||||||
|
(pico . "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css")))
|
||||||
|
|
||||||
|
(setq primary-css (alist-get 'pico-amber wfot-styles))
|
||||||
|
|
||||||
|
(defun wfot-default (page-tree pages _global)
|
||||||
|
"willfullyobtuse default render function
|
||||||
|
|
||||||
|
See `one-is-page', `one-render-pages' and `one-default-css'."
|
||||||
|
(let* ((title (org-element-property :raw-value page-tree))
|
||||||
|
(path (org-element-property :CUSTOM_ID page-tree))
|
||||||
|
(content (org-export-data-with-backend
|
||||||
|
(org-element-contents page-tree)
|
||||||
|
'one-ox nil))
|
||||||
|
(website-name (one-default-website-name pages))
|
||||||
|
(nav (one-default-nav path pages)))
|
||||||
|
(jack-html
|
||||||
|
"<!DOCTYPE html>"
|
||||||
|
`(:html
|
||||||
|
(:head
|
||||||
|
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||||
|
(:link (@ :rel "stylesheet" :href primary-css))
|
||||||
|
(:link (@ :rel "stylesheet" :href "wfot.css"))
|
||||||
|
(:title ,title))
|
||||||
|
(:body
|
||||||
|
(:header (:a (@ :href "/") ,website-name))
|
||||||
|
(:main
|
||||||
|
(:section (:h1.title
|
||||||
|
,(if (not (string= path "/"))
|
||||||
|
`(:div.title (:h1 ,title))
|
||||||
|
'(:div.title-empty))))
|
||||||
|
(:section (:article ,content))))))))
|
||||||
|
|
||||||
|
(defun wfot-default-home-list-pages (page-tree pages _global)
|
||||||
|
"Default render function to use in the home page that lists pages.
|
||||||
|
|
||||||
|
See `one-is-page' for the meaning of PAGE-TREE and PAGES.
|
||||||
|
|
||||||
|
Also see `one-render-pages' and `one-default-css'."
|
||||||
|
(let* ((title (org-element-property :raw-value page-tree))
|
||||||
|
(content (org-export-data-with-backend
|
||||||
|
(org-element-contents page-tree)
|
||||||
|
'one-ox nil))
|
||||||
|
(website-name (one-default-website-name pages))
|
||||||
|
;; All pages but the home pages
|
||||||
|
(pages-list (wfot-default-pages pages "/.+")))
|
||||||
|
(jack-html
|
||||||
|
"<!DOCTYPE html>"
|
||||||
|
`(:html
|
||||||
|
(:head
|
||||||
|
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||||
|
(:link (@ :rel "stylesheet" :href primary-css))
|
||||||
|
(:link (@ :rel "stylesheet" :href "wfot.css"))
|
||||||
|
(:title ,title))
|
||||||
|
(:body
|
||||||
|
(:header (:a (@ :href "/") ,website-name))
|
||||||
|
(:main
|
||||||
|
(:div/home-list-pages ,content)
|
||||||
|
(:div/pages (:ul ,(reverse pages-list)))))))))
|
||||||
|
|
||||||
|
(defun wfot-default-pages (pages &optional filter)
|
||||||
|
"Return `jack-html' list of PAGES component.
|
||||||
|
|
||||||
|
If FILTER is non-nil, a page is listed only when its path (value
|
||||||
|
of `:one-path' property) matches FILTER regexp.
|
||||||
|
|
||||||
|
Evaluating the following form
|
||||||
|
|
||||||
|
(wfot-default-pages
|
||||||
|
\\='((:one-title \"HOME\" :one-path \"/\")
|
||||||
|
(:one-title \"FOO-1\" :one-path \"/foo-1/\")
|
||||||
|
(:one-title \"FOO-2\" :one-path \"/foo-2/\")))
|
||||||
|
|
||||||
|
returns:
|
||||||
|
|
||||||
|
(:ul
|
||||||
|
(:li (:a (@ :href \"/\") \"HOME\"))
|
||||||
|
(:li (:a (@ :href \"/foo-1/\") \"FOO-1\"))
|
||||||
|
(:li (:a (@ :href \"/foo-2/\") \"FOO-2\")))
|
||||||
|
|
||||||
|
And evaluating the following form with the filter \"/.+\"
|
||||||
|
|
||||||
|
(wfot-default-pages
|
||||||
|
\\='((:one-title \"HOME\" :one-path \"/\")
|
||||||
|
(:one-title \"FOO-1\" :one-path \"/foo-1/\")
|
||||||
|
(:one-title \"FOO-2\" :one-path \"/foo-2/\"))
|
||||||
|
\"/.+\")
|
||||||
|
|
||||||
|
returns a list which doesn't include the home page:
|
||||||
|
|
||||||
|
(:ul
|
||||||
|
(:li (:a (@ :href \"/foo-1/\") \"FOO-1\"))
|
||||||
|
(:li (:a (@ :href \"/foo-2/\") \"FOO-2\")))"
|
||||||
|
(when-let ((li-items
|
||||||
|
(delq nil
|
||||||
|
(mapcar
|
||||||
|
(lambda (page)
|
||||||
|
(let ((href (plist-get page :one-path))
|
||||||
|
(title (plist-get page :one-title))
|
||||||
|
(date (org-element-property :DATE (plist-get page :one-page-tree))))
|
||||||
|
(when (string-match-p (or filter ".*") href)
|
||||||
|
`(:li (@ :class "post-item") (:div (@ :class "post-date") ,date) (:a (@ :href ,href) (:span ,title)) ))))
|
||||||
|
pages))))
|
||||||
|
`(:ul ,@li-items)))
|
||||||
|
|
||||||
|
|
||||||
|
(defun wfot-default-home (page-tree pages _global)
|
||||||
|
"Default render function to use in the home page.
|
||||||
|
|
||||||
|
See `one-is-page' for the meaning of PAGE-TREE and PAGES.
|
||||||
|
|
||||||
|
Also see `one-render-pages' and `one-default-css'."
|
||||||
|
(let* ((title (org-element-property :raw-value page-tree))
|
||||||
|
(content (org-export-data-with-backend
|
||||||
|
(org-element-contents page-tree)
|
||||||
|
'one-ox nil))
|
||||||
|
(website-name (one-default-website-name pages)))
|
||||||
|
(jack-html
|
||||||
|
"<!DOCTYPE html>"
|
||||||
|
`(:html
|
||||||
|
(:head
|
||||||
|
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||||
|
(:link (@ :rel "stylesheet" :href primary-css))
|
||||||
|
(:link (@ :rel "stylesheet" :href "wfot.css"))
|
||||||
|
(:title ,title))
|
||||||
|
(:body
|
||||||
|
(:header ,website-name)
|
||||||
|
(:main
|
||||||
|
(:div/home ,content)))))))
|
||||||
|
|
||||||
|
(defun wfot-default-with-sidebar (page-tree pages global)
|
||||||
|
"Default render function with a sidebar listing PAGES.
|
||||||
|
|
||||||
|
See `one-is-page' for the meaning of PAGE-TREE and GLOBAL.
|
||||||
|
|
||||||
|
Also see `one-default-sidebar', `one-render-pages' and `one-default-css'."
|
||||||
|
(wfot-default-sidebar page-tree pages global))
|
||||||
|
|
||||||
|
(defun wfot-default-sidebar (page-tree pages _global &optional with-toc)
|
||||||
|
"Return a HTML string with PAGES listed in a sidebar.
|
||||||
|
|
||||||
|
The arguments PAGE-TREE, PAGES and _GLOBAL are the same as
|
||||||
|
render functions take (See `one-is-page').
|
||||||
|
|
||||||
|
When WITH-TOC is non-nil, add the table of content of PAGE-TREE
|
||||||
|
in the HTML string.
|
||||||
|
|
||||||
|
This function is meant to be used by `one-default-with-sidebar'
|
||||||
|
and `one-default-doc' render functions.
|
||||||
|
|
||||||
|
See `one-render-pages', `one-default-css' and `wfot-default-pages'."
|
||||||
|
(let* ((title (org-element-property :raw-value page-tree))
|
||||||
|
(path (org-element-property :CUSTOM_ID page-tree))
|
||||||
|
(content (org-export-data-with-backend
|
||||||
|
(org-element-contents page-tree)
|
||||||
|
'one-ox nil))
|
||||||
|
(website-name (one-default-website-name pages))
|
||||||
|
(pages-list (wfot-default-pages pages))
|
||||||
|
(headlines (cdr (one-default-list-headlines page-tree)))
|
||||||
|
(toc (one-default-toc-component headlines))
|
||||||
|
(nav (one-default-nav path pages)))
|
||||||
|
(jack-html
|
||||||
|
"<!DOCTYPE html>"
|
||||||
|
`(:html
|
||||||
|
(:head
|
||||||
|
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||||
|
(:link (@ :rel "stylesheet" :href primary-css))
|
||||||
|
(:link (@ :rel "stylesheet" :href "wfot.css"))
|
||||||
|
(:title ,title))
|
||||||
|
(:body
|
||||||
|
;; sidebar-left and sidebar-main are for small devices
|
||||||
|
(:div/sidebar-left (@ :onclick "followSidebarLink()")
|
||||||
|
(:div (:div "Pages"))
|
||||||
|
,pages-list)
|
||||||
|
(:div/sidebar-main)
|
||||||
|
(:div/sidebar-header
|
||||||
|
(:svg/hamburger (@ :viewBox "0 0 24 24" :onclick "sidebarShow()")
|
||||||
|
(:path (@ :d "M21,6H3V5h18V6z M21,11H3v1h18V11z M21,17H3v1h18V17z")))
|
||||||
|
(:a (@ :href "/") ,website-name))
|
||||||
|
(:div/sidebar-content
|
||||||
|
(:div/sidebar ,pages-list)
|
||||||
|
(:article
|
||||||
|
,(if (not (string= path "/"))
|
||||||
|
`(:div.title (:h1 ,title))
|
||||||
|
'(:div.title-empty))
|
||||||
|
,(when with-toc toc)
|
||||||
|
,content
|
||||||
|
,nav)))
|
||||||
|
(:script "
|
||||||
|
function sidebarShow() {
|
||||||
|
if (window.innerWidth < 481)
|
||||||
|
document.getElementById('sidebar-left').style.width = '75vw';
|
||||||
|
else {
|
||||||
|
document.getElementById('sidebar-left').style.width = 'min(300px, 34vw)';
|
||||||
|
}
|
||||||
|
document.getElementById('sidebar-main').setAttribute('onclick', 'sidebarHide()');
|
||||||
|
document.getElementById('sidebar-main').style.display = 'block';
|
||||||
|
}
|
||||||
|
function sidebarHide() {
|
||||||
|
document.getElementById('sidebar-left').style.width = '0';
|
||||||
|
document.getElementById('sidebar-main').style.display = 'none';
|
||||||
|
}
|
||||||
|
")))))
|
||||||
|
|
||||||
|
|
||||||
|
(defun wfot-default-doc (page-tree pages global)
|
||||||
|
"Default render function with a sidebar listing PAGES and the table of content.
|
||||||
|
|
||||||
|
See `one-is-page' for the meaning of PAGE-TREE and GLOBAL.
|
||||||
|
|
||||||
|
Also see `one-default-sidebar', `one-render-pages' and `one-default-css'."
|
||||||
|
(wfot-default-sidebar page-tree pages global 'with-toc))
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
./scripts/hugo
|
emacs --eval "(org-publish 'blog' t)"
|
||||||
rsync --delete-after -avzr public/ verin.brechtel:/nas/www/willfullyobtuse.com
|
rsync --delete-after -avzr public/ verin.brechtel:/nas/www/willfullyobtuse.com
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
docker run -p 1313:1313 --rm --workdir=/site -v .:/site hugomods/hugo:base hugo $@
|
|
||||||
22
setup.org
Normal file
22
setup.org
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+author: James Brechtel
|
||||||
|
#+email: me@jamesbrechtel.com
|
||||||
|
#+bind: org-export-publishing-directory "./public"
|
||||||
|
#+html_head_writ: <link rel="stylesheet" href="//writ.cmcenroe.me/1.0.4/writ.min.css" type="text/css">
|
||||||
|
#+html_head_bahunya: <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kimeiga/bahunya/dist/bahunya.min.css">
|
||||||
|
#+html_head_awsm: <link rel="stylesheet" href="https://unpkg.com/awsm.css/dist/awsm.min.css" type="text/css">
|
||||||
|
#+html_head_simple: <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
||||||
|
#+html_head_holiday: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2" />
|
||||||
|
#+html_head_mvp: <link rel="stylesheet" href="https://unpkg.com/mvp.css">
|
||||||
|
#+html_head: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" />
|
||||||
|
#+html_head_pico_amber: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.amber.min.css">
|
||||||
|
#+html_head_tacit: <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css"/>
|
||||||
|
#+html_container: main
|
||||||
|
#+html_content_class: container
|
||||||
|
#+options: html-link-use-abs-url:nil html-postamble:nil
|
||||||
|
#+options: html-preamble:t html-scripts:nil html-style:nil
|
||||||
|
#+options: html5-fancy:t tex:t
|
||||||
|
#+options: author:t broken-links:mark c:nil creator:nil f:t tasks:t toc:nil todo:nil
|
||||||
|
#+OPTIONS: num:nil
|
||||||
|
#+language: en
|
||||||
|
:END:
|
||||||
20
simple-css.org
Normal file
20
simple-css.org
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
#+subtitle:
|
||||||
|
:END:
|
||||||
|
** Very simple CSS frameworks
|
||||||
|
|
||||||
|
*** Minimal CSS / fancy resets
|
||||||
|
I really like simple drop-in CSS resets like the one I use for this site.
|
||||||
|
|
||||||
|
At the time of writing, I'm using [[https://picocss.com/][Pico]] but I also considered [[https://yegor256.github.io/tacit/][tacit]]
|
||||||
|
|
||||||
|
The idea is that they provide nice default styling of HTML elements out of the box without the need to reference any specific classes.
|
||||||
|
|
||||||
|
The idea works well for sites that are much more content than layout - like this one.
|
||||||
|
|
||||||
|
Using tacit is a matter of incluing this link tag in the page's HEAD element:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||||
|
#+END_SRC
|
||||||
Submodule themes/dark-theme-editor deleted from c1e4ccc366
35
vi-everywhere.org
Normal file
35
vi-everywhere.org
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
#+SETUPFILE: setup.org
|
||||||
|
#+keywords: vim
|
||||||
|
#+subtitle:
|
||||||
|
:END:
|
||||||
|
** vi modal editing in most places
|
||||||
|
For my sake, I prefer to have Vim bindings in as many places as
|
||||||
|
possible.
|
||||||
|
|
||||||
|
Most shells can be configured to use Vim bindings by putting =set -o vi=
|
||||||
|
somewhere in your shell startup script.
|
||||||
|
|
||||||
|
If you're using ZSH then you'll probably want an additional binding to
|
||||||
|
restore CTRL-R reverse history search.
|
||||||
|
|
||||||
|
=bindkey '^R' history-incremental-search-backward=
|
||||||
|
|
||||||
|
For CLI tools that use the =readline= library then you can configure its
|
||||||
|
input mode using a =.inputrc= file in your =$HOME= directory.
|
||||||
|
|
||||||
|
This affects REPLs like =ghci= and tools like =psql=.
|
||||||
|
|
||||||
|
#+begin_src txt
|
||||||
|
set editing-mode vi
|
||||||
|
$if mode=vi
|
||||||
|
|
||||||
|
set keymap vi-command
|
||||||
|
# these are for vi-command mode
|
||||||
|
Control-l: clear-screen
|
||||||
|
|
||||||
|
set keymap vi-insert
|
||||||
|
# these are for vi-insert mode
|
||||||
|
Control-l: clear-screen
|
||||||
|
$endif
|
||||||
|
#+end_src
|
||||||
Reference in New Issue
Block a user