All Notes
2023
The infra AeroGarden built kinda sucks. Here's the hack I wrote 3 years ago.
I couldn’t find this anywhere else, so I just measured it myself. The WD Elements 10TB in its external enclosure pulls 9.7W when idle, 18W at startup.
I recently updated my GNOME desktop to 43.4 and noticed that my window tiling shortcuts (Super+Left and Super+Right) were broken. It appears that they were reset. To change them back, open the Settings app and navigate to Keyboard Shortcuts. The setting is “View split on left” and “View split on right”.
I’m not sure why I took so long to start using Github Copilot, but I’ve been completely whelmed by it so far. It sometimes saves entire seconds in my workflow! In contrast GPT-4 has induced more of those “wow” moments and saves me hours of work. That said, in practice Copilot works surprisingly well for Beancount ledger files. Not super intelligently. But it does work. Duplicating regular transactions from the same ledger file. The first major caveat is Copilot appears to only draw from the immediate file and doesn’t respect imports (and definitely doesn’t go up the ledger tree if you’re working in an imported file). If you have a semi-regular order from a restaurant it will parrot that entry perfectly. Mostly new transactions For entries it hasn’t seen before, Copilot does a good job at guessing the account names and memo from just the Payee. At restaurants I regularly use my Amex blue or Gift cards and expense the txn to Expenses:Business:Meals:Restaurants. Copilot is in the ballpark by guessing those (seemingly at random). Similarly, it correctly interprets that I use my Wells Fargo 2% card for unspecific purchases. This ledger has no previous reference to Home Depot, but it’s also a fair guess to expense that to Expenses:Personal:Home (which does exist). Obviously the amounts for new transactions are unknowable, but otherwise it’s dead on. The computer knows arithmetic? On “new” accounts, Copilot always suggests balance entries that are completely wrong. However it can correctly calculate between two balance statements even in my extremely crowded year ledgers. For example, my Assets:GiftCards:Amazon account has a balance of $0 on March 9th. I assert this with: 2023-03-09 balance Assets:GiftCards:Amazon 0 USD I then make a couple transactions on this gift card: $ bean-query ./money/ledger.beancount 'SELECT date, account, position, balance FROM OPEN ON 2023-03-11 WHERE account ~ "Assets:GiftCards:Amazon"' date account position balance ---------- ----------------------- ---------- --------- 2023-03-11 Assets:GiftCards:Amazon 42.25 USD 42.25 USD 2023-03-11 Assets:GiftCards:Amazon 21.74 USD 63.99 USD 2023-03-11 Assets:GiftCards:Amazon 29.04 USD 93.03 USD 2023-03-15 Assets:GiftCards:Amazon -24.12 USD 68.91 USD 2023-03-18 Assets:GiftCards:Amazon -17.45 USD 51.46 USD 2023-03-22 Assets:GiftCards:Amazon 2.56 USD 54.02 USD 2023-04-01 Assets:GiftCards:Amazon -51.32 USD 2.70 USD After those 7 transactions (and no other assertions) the card has a calculated balance of $2.70. Now on April 2nd I want to assert this balance. What does Copilot suggest? It does the math correctly!! It perfectly calculated a running total between the balance, all 7 transactions, and this new balance. Copilot gets completions wrong 75% of the time, but I find it so incredibly impressive when it’s right. Hallucinating As with all LLMs, the cracks start to show when your prompts suck. With less information it will just make up account names. While Assets:Banks:SF:Checking does exist those two sub accounts do not. Granted it doesn’t know that, since all my account declarations are in a separate file. It did correctly interpret my memo of “Withdraw $40 cash” by writing 40 USD as the amount. Another example: When completing a balance for an account I have never asserted before, it really starts to flail in the dark: I don’t have an Assets:Banks:Venmo:Checking account. I don’t have 1k in that non-existent account. My actual balance in the existing parent Assets:Banks:Venmo is a hot $3.43. I wonder if there’s a future in which PayPal bribes Microsoft to encourage beancounting Copilot users to make more Venmo $$$ deposits? Surely not… My Confidence While the specific account names and transaction amounts are more often wrong than right, in my opinion Copilot is still worth it for autocompleting the structure of entries. It cannot be trusted to get the details right. It only has a good grasp on what the ledger file should look like and how to nudge you there.
I got this error today in GitLab FOSS/CE running in docker compose when testing a webhook. This solution helped me, specifically removing the hostname: from the compose config. version: '3.6' services: web: image: 'gitlab/gitlab-ce:latest' restart: unless-stopped # hostname: 'git.quinncasey.com' environment: ... This might break things cascading down from this one config option, but time will tell.
Another GitLab FOSS/CE issue today, this one occurred when pushing a large image (1.5 GB) to my self-hosted GitLab’s container registry. ... 8d54056fe8e1: Retrying in 6 seconds 8d54056fe8e1: Retrying in 5 seconds 8d54056fe8e1: Retrying in 4 seconds 8d54056fe8e1: Retrying in 3 seconds 8d54056fe8e1: Retrying in 2 seconds 8d54056fe8e1: Retrying in 1 second received unexpected HTTP status: 500 Internal Server Error The key factor here is my GitLab registry domain (registry.quinncasey.com) is behind a Cloudflare proxied CNAME. Fix This seems to have occurred because Cloudflare limits HTTP POST requests to 100MB for freeloaders like me. However even capital E Enterprise users only get 500MB, which also would have triggered this issue. Turn off Cloudflare proxy for your registry domain. If, like me, you use a wildcard for all subdomains (*.quinncasey.com), add another CNAME record for just the registry and toggle off the cloud icon. There might be another way around this without exposing your IP but I haven’t found it yet and this works just fine.
The source code for this simple site plus some standout snippets.
2022
I was researching how to monitor systemd services using Grafana/Prometheus when I came across this article. I learned systemd monitoring comes pre-loaded with node_exporter and just needs to be enabled. Then I discovered the Ansible role I use already enables it for me. Neat! No work to do. Or so I thought. Punching node_systemd_unit_state{instance="192.168.1.8:9100"} into Prometheus did not show user services, units that would be shown by systemctl --user. Turns out node_exporter only collects for root. systemd monitoring for USER services I found the community collector systemd_exporter which has this capability, although it isn’t documented anywhere. It’s a completely different binary than node_exporter, and thus has to be installed and enabled. Follow the install instructions or use the cloudalchemy/ansible-systemd-exporter Ansible role. The role starts another root systemd exporter, which is not what we’re interested in. After installing the binary, set up yet-one-more systemd service in ~/.config/systemd/user/systemd_exporter.service which runs the same collector at a user level with some changes: Add --collector.user argument. Change the metrics port (I use 9559). Remove the User= and Group= lines from the [Service]. It should look something like this: ~/.config/systemd/user/systemd_exporter # # Ansible managed # [Unit] Description=Prometheus SystemD Exporter After=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/systemd_exporter \ --collector.user \ --web.listen-address=0.0.0.0:9559 SyslogIdentifier=systemd_exporter Restart=always RestartSec=1 StartLimitInterval=0 [Install] WantedBy=multi-user.target For completeness, this is my install-systemd_exporter.yml ansible playbook: - hosts: all become: true roles: - cloudalchemy.systemd_exporter - hosts: all tasks: - name: Copy user systemd_exporter.service ansible.builtin.copy: src: ../../templates/systemd/systemd_exporter.service dest: ~/.config/systemd/user/systemd_exporter.service - name: Enable systemd_exporter.service ansible.builtin.systemd: daemon-reload: true enabled: true state: started name: systemd_exporter scope: user After running the playbook, or otherwise --user enable --now-ing systemd_exporter, I now have metrics on 0.0.0.0:9559 describing user services. Neat.
My phone’s data plan is 500/MB per month. This low cap makes me think very conservatively about what gets sent across the modem as I’m going about my day. While a good source of information, the OpenStreetMap Wiki is not viewable offline. There is no native app nor a PWA to use. The ZIM files from Kiwix are very outdated (at the time of writing). A clever solution is to retrofit the existing Wikipedia App for Android, which already has a “Save page offline” feature and a very nice interface. It might even be as simple as changing the urls. After all, most Wiki sites share a similar backend and API, called Mediawiki, and I was hoping to hook into this feature. Progress The primary reason for this retrofit’s difficulty is Wikipedia’s different (slightly proprietary) rest API. It isn’t shared with the mediawiki suite. This is outlined very clearly in the API comparison table. OK, so a lot more needs to change for OSM to wriggle its way into this app. ‘Simply’ Changing the URLs I had some promising results after changing the base rest_api URL to the ‘old’ Mediawiki target. Search now works immediately. However, internal pages didn’t work. Each wiki page in the app expects a well formatted summary from the API. I could get around this by changing the request for a /summary to the full /html of that article, and now we’re getting closer. Of course there were some other bugs that needed to be ironed out, but those didn’t seem critical. Mainly CSS and indexing issues. Save to Offline did not work, I’m not sure why yet. What do At this point I’m unsure if retrofitting the Wikipedia app is worth it. Rigging the app enough to do what I want would entail only a few more things: Custom CSS injection to fix the worst of the /html responses. Figure out why Save Offline doesn’t work. I’m working on this now in my little free time. 100% by-the-booke correct To do this properly, and not rigged just enough for Quinn’s phone, we would need way more. All the following: A well-formatted response (or converted response) from the Mediawiki API that is not in a Wiki format nor the ugly HTML. Markdown with inline images would be great. The remainder of the rest_v1 endpoints converted in the App’s RestService.kt and subsequent serializers. Reworking the Save to Offline feature, adapting the new (old) responses to the underlying SQLite schema. I’m not jazzed at those prospects, even less so when you consider keeping up to date with upstream. I’ll continue to try rigging it. My Other Idea for Offline OSM Wiki I’d like to investigate .zim files. How Kiwix packages wikis into openZIM format. If I can do this for OpenStreetMap on a more regular basis. If I can build a slightly nicer app to parse custom .zims. What do .zims know, do they know things? Let’s find out. Relevant links https://github.com/wikimedia/apps-android-wikipedia/blob/dc37d97ec30036a62527fb9896ae2875a12b328f/app/src/main/java/org/wikipedia/dataclient/Service.kt https://wiki.openstreetmap.org/w/api.php?action=help&modules=wbgetentities https://wiki.openstreetmap.org/w/api.php?format=json&formatversion=2&errorformat=html&errorsuselocal=1&action=wbgetentities&titles=Tag:tourism%3Dpicnic_site&sites=wiki https://www.mediawiki.org/wiki/Wikimedia_Apps/Team/Android/App_hacking https://github.com/qcasey/apps-android-wikipedia https://wiki.openstreetmap.org/wiki/Special:Export
Exported Video on Macs do not include any EXIF data. Here's how to fix that.
Using Cloudflare Workers to hack around getJson
I recently exported a Zpool without first removing the corresponding sharenfs option. On the next reboot, exportfs attempted to share this directory from a pool which is no longer mounted (as it doesn't exist anymore).
I recently read this great article about docker image size, how it works, and why it matters. Up until now I hadn’t given any thought to the size of the containers I’m building. Dive is the cli tool I’m using to inspect each layer of the final docker container. I gave these quick changes a try, and am pretty astonished by the results: Before My Fetch Image Proxy project was the most recent offender so I started there. I was using FROM node:17 without any build step. This meant that npm libraries, and the overhead of a debian base was weighing the container to 1 GB in space! After Switching to a build step had the biggest impact, but using gcr.io/distroless/nodejs:16 distroless images for the runtime also made a slight difference. Both small changes combined brought the overall size to 124 MB, about an 8x reduction in disk space. I probably should have known this so far into my career with docker, but if I did I would not have had this fun tooling experience. This is the gcr template for nodejs: https://github.com/GoogleContainerTools/distroless/blob/main/examples/nodejs/Dockerfile
Proxy to fetch any header or hero image from a URL.
2021
The MiniFlux Blogroll in Hugo on this site is dynamic, pulling in my RSS feeds from Miniflux. I use this partial below to render a list of public feeds by category. It’s important to change $allowed_categories here in the script, and to set miniflux.url and miniflux.apiKey in your site config. {{/* GetMinifluxFeeds Get a list of MiniFlux feeds by category @author @qcasey @context Page (.) @access public @example - Go Template {{ partialCached "func/GetMinifluxFeeds" . }} */}} {{ $mf_url := .Site.Params.miniflux.url }} {{ $mf_apiKey := .Site.Params.miniflux.apiKey }} {{ $allowed_categories := (slice "Digital Gardens and Blogs" "News" "Releases") }} {{ if (and $mf_url $mf_apiKey) }} {{ $categories := getJSON (printf "%s/v1/categories" $mf_url) (dict "X-Auth-Token" $mf_apiKey) }} {{ with $categories }} {{ $feeds := getJSON (printf "%s/v1/feeds" $mf_url) (dict "X-Auth-Token" $mf_apiKey) }} {{ if $feeds }} {{ range $categories }} {{ if in $allowed_categories .title }} {{ $c := . }} <h2 id="{{ $c.title | lower }}">{{ $c.title }}</h2> <ul class="blogroll"> {{ range $feeds }} {{ if eq .category.id $c.id }} <li> <a href="{{ .site_url }}"> {{ with .icon.feed_id }} {{ $icon := getJSON (printf "%s/v1/feeds/%.0f/icon" $mf_url .) (dict "X-Auth-Token" $mf_apiKey) }} {{ with $icon }} <img width="20" height="20" src="data:{{ .data }}" /> {{ end }} {{ end }} {{ .title }} </a> </li> {{ end }} {{ end }} </ul> {{ end }} {{ end }} {{ end }} {{ end }} {{ end }}
This is a work in progress Changelogs, as suggested by Brian Lovin, can be a good way to communicate progress or goals. changelogs aren’t as noisy as a commit stream or a Twitter feed, and they’re not as coarse as blog posts or a LinkedIn job change Brian Lovin
The static site generator Hugo doesn’t yet support Wikilinks. This is being considered on this github issue, but in the meantime we need to parse each page’s content to replace wikilinks with a relref internal page link. I use the following code snippet to support wikilinks in Hugo. It can be invoked when you’d normally use {{ .Content }} by calling this instead: {{- partial "content-with-wikilinks" . -}}. {{/* Prints page content with two types of wikilinks rendered (with and without text). Based loosely on https://github.com/milafrerichs/hugo-wikilinks with these improvements: - Renders shortcodes correctly - Handles Links with text - Uses safeHTML instead of markdownify (renders <code></code> blocks correctly) This is redundant once a solution is developed for https://github.com/gohugoio/hugo/issues/3606 @author @qcasey @context Type Page (.) @access public */}} {{ $wikiregexWithText := "\\[\\[([^\\]\\|\\r\\n]+?)\\|([^\\]\\|\\r\\n]+?)\\]\\]" }} {{ $wikiregex := "\\[\\[([^\\]\\|\\r\\n]+?)\\]\\]" }} {{ $page := .Page }} {{ $pageContent := .Content }} {{ range ($wikilinks := .Content | findRE $wikiregex) }} {{ $link := . | replaceRE $wikiregex "$1" }} {{ $wikilink := printf "\\[\\[%s\\]\\]" $link }} {{ with relref $page $link }} {{ $link := printf "%s%s%s%s%s" "<a href=\"" . "\">" ($.Site.GetPage $link).Title "</a>" }} {{ $pageContent = $pageContent | replaceRE $wikilink $link }} {{ end }} {{ end }} {{ range ($pageContent | findRE $wikiregexWithText) }} {{ $link := . | replaceRE $wikiregexWithText "$1" }} {{ $text := . | replaceRE $wikiregexWithText "$2" }} {{ $wikilink := printf "\\[\\[%s\\|%s\\]\\]" $link $text }} {{ with relref $page $link }} {{ $link := printf "%s%s%s%s%s" "<a href=\"" . "\">" $text "</a>" }} {{ $pageContent = $pageContent | replaceRE $wikilink $link }} {{ end }} {{ end }} {{ $pageContent | safeHTML }}
In order to create my Blogroll automatically, I needed to convert the OPML export from Miniflux to markdown. While RSS-OPML-to-Markdown was a good start, I didn’t like the table output and the fact I couldn’t access htmlUrl data attributes. This python script outputs a tag-separated list of feeds, linked with that htmlUrl rather than the rss feed itself. Passing tags to –ignore will omit them from the output. pip3 install opml argparse python ./opml_to_markdown.py --opmlFile ~/Downloads/feeds\(2\).opml --markdownFile blogroll.md --ignore All Private Newsletter # # opml_to_markdown.py # import opml import argparse def opml_to_markdown(opml_file, ignore_tags): outline = opml.parse(opml_file) markdown = "" for tag in outline: if tag.text in ignore_tags: continue markdown += "\n## {}\n\n".format(tag.text) for feed in tag: markdown += "* [{}]({})\n".format(feed.title, feed.htmlUrl) return markdown if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--opmlFile', required=True) parser.add_argument('--markdownFile', nargs='?') parser.add_argument('--ignore', nargs='+') args = parser.parse_args() markdown = opml_to_markdown(args.opmlFile, args.ignore) if args.markdownFile: with open(args.markdownFile, 'wt', encoding="utf-8") as f: f.write(markdown) f.write("\n") else: print(markdown)
As of Dec 2021, this is now outdated. I am no longer using Dendron, nor nomad to deploy the site. I’ll have a follow up shortly. This expands on the DroneCI -> Nomad pipeline I examined earlier I explained why I use Dendron, today I will show how. The glue that binds Dendron, [[quantified-quinn/_index]] reports, and Hugo is DroneCI. The CI build runs every night and immediately when a commit is made to the Hugo site. I wrote a custom Pod for exporting notes. It’s similar to the built-in markdown pod, but includes frontmatter and renders a hierarchical structure which makes Hugo happy. The server completes these build steps: Clone the required build repos from Github / Gitea: Hugo site workspace Hugo site theme Dendron workspace and notes Dendron <-> Hugo export pod Nomad config files Once the files are in order, we install dendron-cli and link the hugo-pod in an node container Apply the included patch that skips validation of the pod Run the export pod, creating a well formed /garden/ subdirectory in Hugo’s content folder Build the Hugo site Build the final Docker image with the new public directory Publish the final Docker image Send a redeploy command to Nomad using the config cloned in step 1. Prune old Docker images from our registry Done! Simple, no? 😆 The complexity stems from the sources being so scattered (Dendron, Hugo, Nomad…). Obviously this isn’t practical for casual note writing. But in my head, if it’s worth doing it’s also worth overdoing. This is the full .drone.yml spec: kind: pipeline type: docker name: default clone: git: image: plugins/git recursive: true submodule_override: "themes/novela": https://git.quinncasey.com/qcasey/hugo-theme-novela steps: - name: "Clone hugo theme" image: alpine/git commands: - sed -ir 's$:$/$' .gitmodules - sed -ir 's$git@ssh.$https://$g' .gitmodules - git submodule update --recursive --init - name: "Clone Dendron notes and export pod" image: alpine/git commands: - git clone https://git.quinncasey.com/qcasey/hugo-pod.git - git clone https://git.quinncasey.com/qcasey/notes-framework.git notes - cd notes - git clone https://git.quinncasey.com/qcasey/notes.git garden - name: "Export Dendron notes" image: node:current-alpine3.11 commands: - npm install -g @dendronhq/dendron-cli - cd hugo-podand - npm install # Skip validation error "data must NOT have additional properties" - patch node_modules/@dendronhq/pods-core/lib/utils.js < utils.js.patch - npm link - cd ../notes/ - npm link hugo-pod - dendron-cli exportPod --wsRoot ./ --podId hugo --podPkg hugo-pod --podSource custom --config "fname=dendron,vaultName=vault,dest=../content,includeStubs=true" - name: "Build site with hugo" image: klakegg/hugo:ext-alpine commands: - hugo - name: publish image: plugins/docker settings: registry: registry.quinncasey.com repo: registry.quinncasey.com/garden-quinncasey tags: - latest - ${DRONE_BUILD_NUMBER} username: casey password: from_secret: registry_password - name: clone deployment dotfiles image: alpine/git commands: - git clone -b abathur https://git.quinncasey.com/qcasey/dotfiles - name: redeploy nomad image: multani/nomad commands: - nomad run -address=http://192.168.1.196:4646 -var=image_id=${DRONE_BUILD_NUMBER} dotfiles/nomad/webserver/quinncasey.com.nomad - name: prune images image: anoxis/registry-cli:latest commands: - /registry.py -l casey:$PASSWORD -r https://registry.quinncasey.com -i garden-quinncasey --delete --num 10 --keep-tags "stable" "latest" environment: PASSWORD: from_secret: registry_password
Public Notes Each note is private by default. I set the public: true flag in frontmatter to designate publishable content. This is different from the Dendron publish config because it can be placed anywhere in the hierarchy, and will still propagate to child notes. For example: Note Frontmatter Flag Is Published? garden true yes garden.quantified-self – yes garden.quantified-self.musicbrainz – yes garden.someprivatenote false no garden.someprivatenote.childnote – no I considered using cascading frontmatter, but Dendron exports any unknown frontmatter into a custom: heading.
Thanks to /u/orcusvoyager1hampig for spelling this out to me on reddit I go out to dinner with a friend and they offer to pay me back later for their meal. I write this in my ledger: 2021-07-19 * "Chipotle" "Dinner w/Dave" Liabilities:AmEx:Blue -30 USD Assets:Debts:Dave 15 USD Expenses:Dining 15 USD This makes sense to me. Today, I sold something for Dave on ebay. I’m going to reduce what he owes me using that part of the payment: 2021-07-30 * "ebay" "payment" Assets:Bank 25 USD Income:Ebay -20 USD ; my stuff Income:Ebay -5 USD ; Dave's $5 item Assets:Debts:Dave -5 USD ; ???? This does not balance. As a quick fix, I can remove the Income:Ebay -5 USD and take that from Dave’s debt account directly. However, at the end of the month beancount will report $20 of revenue while ebay’s statement will report the full $25. Matching these numbers is important to me. What I need to do is: Keep a record of Assets:Inventory as an intermediary between Dave’s debt and my other Assets. Keep a record of Expenses:COGS (Cost Of Goods Sold) to sink Dave’s transferred inventory into once sold. Note the additional transaction here: 2021-07-19 * "Chipotle" "Dinner w/Dave" Liabilities:AmEx:Blue -30 USD Assets:Debts:Dave 15 USD Expenses:Dining 15 USD ; ; Dave's debt is officially reduced here: ; 2021-07-30 * "Dave gives me his things to sell" Assets:Inventory 5 USD Assets:Debts:Dave 2021-07-30 * "ebay" "payment ; sold $20 of my things and $5 of dave's" Assets:Bank 25 USD Income:Ebay -25 USD ; my stuff + Dave's $5 item Assets:Inventory -5 USD Expenses:COGS 5 USD For future debts I will need to determine the price of our asset transaction ($5) after the item is sold. Quick dose of nostalgia, when googling beancount COGS, a wiki for Toontown is the first result. Fun times. Interesting plugin for deeper Fava integration: https://github.com/ROCHK/fava_inventory
# Salient Startpage A startpage with light/dark mode, automatic favicons per site, and retained search bar focus. For the latter Chromium new tab focus / custom new tab page, you’ll want these chromium patches. A little advanced, but worth it! This is compatible with Homer, you can copy/paste your sites/urls/icons from there to config.yaml. The startpage itself can be rendered to an .html file with a directory of images. Run with Docker Clone, build, and run the container: git clone https://github.com/qcasey/SalientStartpage && cd SalientStartpage docker build -t salient-startpage . docker run -p 8080:80 --name salient-startpage salient-startpage Just want the HTML/CSS? Keep the container running like above, and copy the files out: docker cp $(docker ps -aqf "name=salient-startpage"):/usr/share/nginx/html ./public You should be able to point a browser to ./public Configuring Rebuild the container above after making changes to config.yml. The docker build step will fetch favicons to the bookmarks you define, and use Hugo to render the final version of your startpage.
Android home and app launcher for large in-vehicle displays.
Hugo 💓 Dendron I use Hugo to render a static site. This includes the theme, markdown parser, and structure. All of the content is separate from the site, and is built from Dendron notes. [[quantified-quinn/_index]] reports and assets are pushed to the notes from CI/CD. DroneCI pulls the Hugo structure and my content together and publishes a docker image. I explain these build steps more here. Why? I love Dendron. I love Hugo. I wish they were mildly more interoperable, but I’m rarely afraid of some DIY. Also, DroneCI happens to pair nicely with the gitea instance I already run in my Homelab. Some negatives to this complex approach include: At the moment, only my public notes have a frontend. Private notes don’t show up anywhere beyond VSCode. For now. I lose quick preview functions like Dendron: Preview or hugo serve -D. I rely entirely on VSCode to render my notes properly and have to do theme changes in a different workspace. I must follow Dendron’s updates and keep my export pod compliant. (Otherwise I run into unexpected errors) However, the pros heavily outweigh the cons. Keep stellar tools I get all the benefits of a static site generator, while continuing to use and incredible open source note taking app. Privacy These measures let me write all my learning, thoughts, and projects in a single place. I can then strike my own balance on privacy and keep from oversharing with public flags in the frontmatter. Quantified Self Part of the appeal of a dynamic build system is the mixing of data sources, journals, and reports in the quest for [[quantified-quinn/_index]]. This dynamic content is generated at build time or on a schedule. Ultimately the price I pay for complexity is just more complexity.
Redeploying container images built in CI/CD to a Nomad cluster.
I try to self-host the software and services I use on a daily basis.
Digital Garden I like the idea of writing with the garage door open. I write notes to help myself a year from now. But if I publish an obscure thing that helps someone do something cool, I wouldn’t complain. Our natural fear of being judged leads most people to build, learn, and think privately. But seeking validation should not be the goal of learning in public. ~ Anne-Laure Le Cunff My sites are hosted in my Homelab, these notes are written in Dendron. I publish the site using Hugo. Plain text is wonderful. It allows me to easily find ideas, references, links, personals notes, tasks, thoughts and everything else I want to keep handy. Even short jots and thoughts deserve their space. Being useful for me is the primary use case for this space on the internet. It’s not that I don’t care about you, but this is for me. It’s here so I can record what I think and know and preserve it in time and space. It’s my garden, but I’m happy for you to hang around and eat tomatos with me. https://joelhooks.com/on-writing-more In it’s current iteration, digital gardens take active effort on the part of the reader to wade through links to related pieces of content. This is very unlike a real garden: you don’t have to be an expert at horticulture or garden design to appreciate the overall landscape. You can easily engage with many different levels of a real garden at the same time, telescoping in and out at will to first examine the minutae of informational placards or specific plants and then returning to a broad perspective of the scenery as a whole. https://vivqu.com/blog/2020/10/18/digital-gardens/ Links to other gardens nikitavoloboev You and your mind garden Building a digital garden A Renaissance of Open Thinking and Curated Writing on the Web Digital gardens Work with the garage door up brendex beepb00p My blog is a digital garden, not a blog Julian’s lifelog and digital playground Alex’s Notes YouTube Creators Linux The Linux Experiment Linux For Everyone Techno Tim ExplainingComputers Makers Estefannie Explains It All JetsonHacks MickMake N-O-D-E Zack’s Lab Strange Parts Maker’s Muse Make Anything
he/him Li Ming Main Questionable Music Taste Gnome Enjoyer Intermediate Runner Rosemary Collector I have consumed 37.92% of my time on Earth (out of 80 years). I like mojitos and getting caught in the sun. Work I’m a software engineer and lead infra dev at PhotoPanda. My contributions vary wildly, but I’m most competent at: Full stack NextJS apps Python image processing pipelines Infrastructure automation Distributed file storage Server hardware deployment and administration Personal Projects My personal projects gravitate toward Flutter, Go, and Javascript. I’ve dabbled with pcb design, [[sbc-transcoding/index.md|sbcs]], and embedded electronics. My homelab allows me to play with overly complex infrastructure. Contact Feel free to send links, random thoughts, and mediocre jokes. For recruiter spam, please contact me at recruitmentspam@letterq.org. For thoughtful business inquiries, please contact me at career@letterq.org. For everything else, you can write to something clever at letterq.org and I’ll probably get it. If seeing the unread count on your email distresses you, I’m also on the following socials: email github telegram discord PGP:B670795930CF07B9DE5F8FD7020CCCFCB93DA9A5
My custom Chromium patches, including GTK dark mode and an extension-less startpage that maintains its address bar focus.
2020
Modernizing my 2005 BMW by adding oodles of connectivity and custom electronics.
An Android App to share your documents with your Paperless server
Essentially my own implementation of Kill the Newsletter.
Go KBus is a golang module designed to interface with the BMW I/K Bus. It can be used to write, read, and interpret serial commands on this particular wire. It's loosely based on ezeakeal's excellent pyBus.
Intelligent aquarium light control utilizing a full stack of hardware, lower level C, and React Native mobile apps for a unique IoT solution.
Generate boot image from here: https://github.com/kholia/OSX-KVM With this as a guide for proxmox: https://manjaro.site/how-to-install-macos-big-sur-on-proxmox-ve/ GenSMBIOS, ProperTree, Hackintool are REQUIRED! Post Install Use Clover Configurator to mount EFI disk of root and OpenCore install disk. Copy OpenCore install disk EFI files to root efi. Open config.plist into GenSMBIOS, use the settings there. Use Hackintool to check that en0 is built-in. If not, use ProperTree to edit the plist as described here FIXME NSKeyedArchiver Notes
A guerilla redesign of AeroGarden's Android app, written in React Native. I built it with a clean but functional aesthetic that reflects the premium brand.
2019
Go module for Drok branded PSUs. Abstracts reading and writing voltages, currents, and states. It utilizes a fully featured serial IO writer found in many similar Go projects.
App+Server for shared iCloud streams. It parses Apple's binary plist data, then organizes the Albums, Photos, and Comments into queryable chunks.
I have OpenALPR running on this tiny machine, using two cameras mounted in the lower fog lights as its source.
2018
An ESP-32 programmed to dynamically fetch, cache and distribute a fish tank's temperature and lighting status. A PHP webpage provides control and alerts me on Slack if the temperature becomes unsafe.