Archive

All published posts

2605 posts latest post 2026-10-24
Publishing rhythm
Sep 2026 | 28 posts

Apr 1 – Apr 7, 20242 years ago

Featured this weekWriting

I am working on a page for htmx-patterns and I ran into a situation with lots of duplication. Especially when i am using tailwind I run into situations where the duplication can get tedious to maintiain. The solution I found is macros.

Now I can use the same code for all of my links, and call the macro to use it.

{% macro link(id, text, boosted=false) -%}
<a
    class="
    {% if id is none %}
      pointer-events-none bg-terminal-950 text-terminal-900 ring-terminal-900
    {% else %}
      bg-terminal-950 hover:bg-terminal-900 hover:text-terminal-400 text-terminal-500 shadow-lg shadow-terminal-300/20 hover:shadow-terminal-300/30 ring-terminal-300
    {% endif %}
      cursor-pointer block text-center font-bold py-2 px-4 rounded w-full ring-1
    "
    {% if id is not none %}
    href="{{ url_for('boosted', id=id) }}"
    {% endif %}
    {% if boosted %}
    hx-boost="true"
    {% endif %}>
    {{ text }}
</a>
{%- endmacro %}

<h2 class='text-3xl font-light mt-0 max-w-xl text-center prose-xl mt-8 text-terminal-500'>
    Boosted Links
</h2>

<div class='flex flex-row gap-4'>
    {{ link(prev_id, 'Previous', boosted=True) }}
    {{ link(next_id, 'Next', boosted=True) }}
</div>

<h2 class='text-3xl font-light mt-0 max-w-xl text-center prose-xl mt-8 text-terminal-500'>
    Normal Links
</h2>

<div class='flex flex-row gap-4'>
    {{ link(prev_id, 'Previous', boosted=False) }}
    {{ link(next_id, 'Next', boosted=False) }}
</div>

Writing

jinja has a loop variable that is very handy to use with htmx. Whether you want to implement a click to load more or an infinite scroll this loop variable is very handy.

{% for person in persons %}
<li
{% if loop.last %}
    hx-get="{{ url_for('infinite', page=next_page) }}"
    hx-trigger="intersect once"
    hx-target="#persons"
    hx-swap='beforeend'
    hx-indicator="#persons-loading"
{% endif %}
    {{ person.name.upper() }} -
    {{ person.phone_number }}
</li>
{% endfor %}

Now for every chunk of contacts that we load we will trigger the infinite scroll by loading more once the last one has intersected the screen.

Writing

Out of the box Starlette does not support url_for with query params. When trying to use url_for with query params it throws the following error.

starlette.routing.NoMatchFound: No route exists for name "infinite" and params "page"

In my searching for this I found starlette issue #560 quite helpful, but not complete, as it did not work for me.

import jinja2

if hasattr(jinja2, "pass_context"):
    pass_context = jinja2.pass_context
else:
    pass_context = jinja2.contextfunction

@pass_context
def url_for_query(context: dict, name: str, **params: dict) -> str:
    request = context["request"]
    url = str(request.url_for(name))
    if params == {}:
        return url
    from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

    # Parse the URL
    parsed_url = urlparse(url)

    # Parse the query parameters
    query_params = parse_qs(parsed_url.query)

    # Update the query parameters with the new ones
    query_params.update(params)

    # Rebuild the query string
    updated_query_string = urlencode(query_params, doseq=True)

    # Rebuild the URL with the updated query string
    updated_url = urlunparse(
        (
            parsed_url.scheme,
            parsed_url.netloc,
            parsed_url.path,
            parsed_url.params,
            updated_query_string,
            parsed_url.fragment,
        )
    )

    if os.environ.get("ENV") in ["dev", "qa", "prod"]:
        updated_url = updated_url.replace("http", "https", 1)

    return updated_url

def get_templates():
    templates = Jinja2Templates(directory="templates")
    templates.env.globals["url_for"] = url_for_query
    return templates

https

If you want url_for to work in production you need some way to convert http to https. Here is how I make it work, for local development I export ENV=local then for each environment that I am running on a server I include it in the list and update ENV appropriately.

    if os.environ.get("ENV") in ["dev", "qa", "prod"]:
        updated_url = updated_url.replace("http", "https", 1)

The route might look something like this.

@infinite_router.get("/")
async def home(request: Request, page: int = 1, n: int = 10):
  ...

To access the home route using url_for in a jinja template you can use the following, once you have applied the url_for_query function as your default url_for

<a href="{{ url_for('home', page=1) }}">Home</a>

Writing

Kind (Kubernetes in Docker) is a tool that makes it easy to create and tear down local clusters quickly. I like to use it to test out new workflows.

Argocd is a continuous delivery tool that makes it easy to setup gitops workflows in kubernetes.

Here is how you can setup a new kind cluster and install argocd into it using helm, the kubernetes package manager.

kind create cluster --name argocd

# your first time through you need to add the argocd repo
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# install argocd into the cluster
helm install argo argo/argo-cd --namespace argocd --create-namespace

# deploy the app of apps
kubectl apply -f apps/apps.yaml

If you want to add repos and apps to your cluster you can use the argo cli to do that, but first you will need forward the argocd port and login.

# Wait until Argo CD API server is available
echo "Waiting for Argo CD API server to be available..."
while ! kubectl wait --for=condition=available --timeout=60s deployment/argo-argocd-server -n argocd; do
  echo "Waiting for Argo CD API server to be ready..."
  sleep 10
done


kubectl port-forward svc/argo-argocd-server -n argocd 8080:443 &
argocd_admin_password=$(kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d)
argocd login localhost:8080 --insecure --username admin --password $argocd_admin_password
argocd repo add https://github.com/fokais-com/app.fokais.git --username waylonwalker --password ${GH_ARGO_PAT}
argocd app create app-fokais-local --repo https://github.com/fokais-com/app.fokais.git --path k8s/overlays/local --dest-server https://kubernetes.default.svc --sync-policy automated --sync-option Prune=true

Writing

feeds

I have several feeds that you can consume my content with. Many posts will cross between them, so if you would like to follow a certain stream of content in your rss reader thats what this is for. This one might feel like it has some extra noise with multiple posts per day, including starts, and instagram style posts,…

Writing

Thoughts

These are generally my thoughts on a web page or some sort of url, except a rare few don’t have a link. These are dual published off of my thoughts.waylonwalker.com site. It’s a fully dynamically rendered site 2000’s style. Posts are stored in a database and instantly available. Almost all of the posts were written in…

Link

Link

I’m really excited about fastapi-observability, an amazing project by blueswen. It’s worth exploring! Observe FastAPI app with three pillars of observability: Traces (Tempo), Metrics (Prometheus), Logs (Loki) on Grafana through OpenTelemetry and OpenMetrics.

Link

If you’re into interesting projects, don’t miss out on taipy, created by Avaiga. Turns Data and AI algorithms into production-ready web applications in no time.

Link

Note

Note

Note

Note

Optimizing SQLite for servers SQLite is often misconceived as a &#34;toy database&#34;, only good for mobile applications and embedded systems because it&#39;s default configuration is optimized for embedded use cases, so most... Sylvain Kerkour · kerkour.com Very interesting article by Sylvain, suggested by Simon Will…

Note

Mar 25 – Mar 31, 20242 years ago

Featured this weekWriting

In my homelab kubernetes cluster I am using kubeseal to encrypt secrets. I have been using it successfully for a few months now wtih great success. It allows me to commit all of my secrets manifests to git with out risk of leaking secrets.

You see kubeseal encrypts your secrets with a private key only stored in your cluster, so only the cluster itself can decrypt them using the kubeseal controller.

kubeseal-post.png

KubeSeal #

https://sealed-secrets.netlify.app/

screenshot of https://sealed-secrets.netlify.app/

installation #

Installation happens in two steps. You need the kubernetes controller and the client side cli to create a sealed secret.

For a more complete instruction see the [docs#installation](https://github.com/bitnami-labs/sealed-secrets?tab=readme-ov-file#installation]

installation - controller #

Warning

context Make sure that you are in the right context before running any kubectl commands.

kubectl config current-context

sealed-secrets is installed using the helm package manager. To install sealed-secrets run the following command.

helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets -n kube-system --set-string fullnameOverride=sealed-secrets-controller sealed-secrets/sealed-secrets

installation - client #

For the client you can check your OS package manager, brew, or the github-releases. For me I found it in the main arch repos.

paru -S kubeseal
# or
sudo pacman -S kubeseal
# or
brew install kubeseal

Note

You will need to install kubeseal on every device that you will want to create sealed secrets on.

Example #

Most of these commands come straight from the docs. From my experience I have always specified the namespace, my projects go per namespace and I don’t have any reason that other namepsaces should see the secret, and if they do I deploy another secret in that namespace.

# Create a json/yaml-encoded Secret somehow:
# (note use of `--dry-run` - this is just a local file!)
echo -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o yaml -n thenamespace > mysecret.yaml

note that the key of the secret is foo and the value is bar

results

apiVersion: v1
data:
  foo: YmFy
kind: Secret
metadata:
  creationTimestamp: null
  name: mysecret
  namespace: thenamespace

Note

The data is base64 encoded.

echo -n bar | base64
# YmFy
# This is the important bit:
kubeseal -f mysecret.yaml -w mysealedsecret.yaml

# At this point mysealedsecret.json is safe to upload to Github,
# post on Twitter, etc.

# Eventually:
kubectl create -f mysealedsecret.yaml -n thenamespace
# sealedsecret.bitnami.com/mysecret created

# Profit!
kubectl get secret mysecret
kubectl get secret mysecret -n thenamespace
# NAME       TYPE     DATA   AGE
# mysecret   Opaque   1      27s

cat mysealedsecret.yaml | kubeseal --validate

echo -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o yaml \
  | kubeseal -o yaml -n thenamespace > mysealedsecret.yaml
echo -n baz | kubectl create secret generic mysecret --dry-run=client --from-file=bar=/dev/stdin -o yaml \
  | kubeseal -o yaml -n thenamespace --merge-into mysealedsecret.yaml

Results

---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: mysecret
  namespace: thenamespace
spec:
  encryptedData:
    bar: AgBLkamltcLfH1dC1JxQ3qd8lJ8aBZF2ARoq3uo055hnzXOy8g2T5liTx5UPvyPV8yyWqABU8eOnwjNhDtzSATvYeBB3fGkucdOZziWEoiNsWTR9ZtFEkod7Ya6uGkzZOJwi3IkrHFVIT9oWZQUxxJZ6vFhPiFcx9Dorr8TNSzG4KOug25+PhWPPiHDgSup5N3CkWCZaYOF7dbZRVSA4nGP1fZxjFByHP4AsdjLCHptyVbkpLRKeiXTkLxfLX4K+JLZGM41S1On5bSP56mCfv1daTJx619kDXkRLw9l21Ot283/L0NMNAiw781AefYMVoO3aHmYgcT6wAtsQAKje9fyL7DQRHt8a5NZOWukp/P6XjdXRz/nfQasQlbSTrRkDpplKIM5/WdPcBoKi+yyoOL0rZ8x1X7YzUI3BggZmzWyEPD01BK1YAHGZnYIZbbCy1JSm8JCBvP+xWMg+i0Z/DCD8nclAhH1GX2Q7/NrNHF//589AJfuriymd2+mk7uaLA4RRsY0l5QeZD6HVAqSv5jWsVQQtSftWmI9vn9oL/Pno7sEUjSDpXPfF4nnsULhxsPEe2DFAMm1kZAjdF06ueF4/x2Fdy80ZQNyycaDx2CWm4z3b14A75WGyOXl2wJZQqxrFCz8el4hD2nH3zQFEzd6AIh49myqVAGuu2qGlYP4p94LJghVa+mQjztLD/2ZUZjY+anQ=
    foo: AgAducXW6iUCY900cPDdmRfuj7tKnh2hY4C1+2hFoAtjyvjepsKNWsiPJ81t8anaMfFPat4ta060l5VtTrceFE8oS/rViz1tvNWGPBjL+GwL/QjkGl6H8ju87vKERKQn5qw7B/V5j24VM8AxOd+/vJNt/IeRIHLubvFft4hyMq4b0xmIxaemBSTxchQX/5364T3VJH2kHaqpqd+JJgQnTbiTQe/XnyZokDX8GSxw4rAbJUJSRUtY9DB9ZDu2zC5VngX+GJjbwHGbv9EKs8LShJIPrD8xHqrDmlSXGkkP01D4A6268Qoi3x5S0H5aqDgtrgBiWsNkzdKwjfrTNx7pKecOi41lyFdffHOGUew4aPPMqjzWR2TEms9WNNQXwnBdDHKMkFsisocF52BolEkcjF8g/u5h2Af92abMu6k16VybJrB7TV1set5A9W7rqG1iXI4+1W6XQfFnpja8xL/zJBvZcyHgeYMNaxa3C3s6PANhPzAUVaXV9eedAkptGJLN13IZj4LujpoAxRKo6bEdydv/5P23R3fx5PgTOpVI7riECAOIg2PThFsEoVCUwStmKCvIx1I2+YixIlv/OiaUWo4lrI/3ve5WGp4ZnuiJPk34JoYAlRbR6+sX14d8Ek6viq/pJUUIfVpNIkNMboUL4u+KpT47eyQ/mWih/KFduQyX9II/vQ+/IJGzEEHIipxAhdmV+K4=
  template:
    metadata:
      creationTimestamp: null
      name: mysecret
      namespace: thenamespace

backing up your sealing key #

kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml >main.key

converting .env files to a secret #

Working with web applications .env is a common way to store credentials. Let’s look at how we can convert these to secrets.

kubectl create secret generic mysecret --from-env-file=.env -n thenamespace --dry-run=client -o yaml > mysecret.yaml

Now you have a secret that looks like this.

apiVersion: v1
data:
  foo: YmFy
kind: Secret
metadata:
  creationTimestamp: null
  name: mysecret
  namespace: thenamespace

Seal it up just like before.

kubeseal -f mysecret.yaml -w mysealedsecret.yaml

Using the secrets #

I typically use the secrets in the container spec.

containers:
  - name: myapp
    envFrom:
      - secretRef:
          name: mysecret
    # You can still have other env vars
    env:
      - name: foo
        value: bar

Sometimes I want to mount the secret as a volume.

containers:
  - name: myapp
    volumeMounts:
      - name: mysecret
        mountPath: /mysecret
volumes:
  - name: mysecret
    secret:
      secretName: mysecret

Image Pull Secrets #

I also need to use imagePullSecrets. Let’s walk through the whole process. Starting with the secret.

kubectl create secret docker-registry regcred --docker-server=myprivateregistry.example.com --docker-username=foo --docker-password=bar --dry-run=client -o yaml

Generates the following secret.

apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJteXByaXZhdGVyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoiYmFyIiwiYXV0aCI6IlptOXZPbUpoY2c9PSJ9fX0=
kind: Secret
metadata:
  creationTimestamp: null
  name: regcred
type: kubernetes.io/dockerconfigjson

---

the secret

Now we we can seal that secret.

kubeseal -f regcred.yaml -w regcred-sealed.yaml

And that gives us the following sealed secret that we can deploy into our cluster.

---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: regcred
  namespace: default
spec:
  encryptedData:
    .dockerconfigjson: AgATYVEywkyYEaoErQbJo6xEZfOnRn1ydNTLkO3Jt/NF/UH+0o9lHpDecRpN0XnVu8xJUcdjkgD9q2XkwP8e6qQDS2mMPTiTNIN+8gbJx97WrD1YQDT0lXBuoyi9I/iwlXxx6MgH/6GY6CeGTz5SRlvoU0Xhlt4d11s7/xapdE8QMLsAReqPEv8oZHEyAxDRrjXX0V+tO8dV+G+GXjUDMBBceLael9rvGzSKIwDVXACVqQhLkB6FoP98M+yyBE46RBNnSnS0ShQM5PprL24HKpRZ43x4RM53KBrQ7R/MxeshafY+B6vUvrolmVox4sud8xngMOcjTO28LLOrck5V8ZiDabhN7ajHEf03IESr1o/ADGf5k9988Vv1txJtsZW0K2mpRu0D7/BLVL9KzbZ5ywULqIoD/Ur2GIGnZqMAKOq4laGp/GJtMKLrhmEvekT397wC/Gf/xdDKVhHf2p4ocsPu7LKFuS5H/Auel/Q5grdn8L5wwrO4VWRv3eJroKh/Hux7Qd7f64O7qdi0XthDocf+gmtjys+Gy72M7tyf8f/O+3oKbS4CWQVTj4ZThMc9znrFnHqt2q/7pAyytTQCpk51wlzOsNvOhCueJM/jmeahaL0LuBrqngqISpnd65sgVzBcZpwK9i2Fckyt0DrZLH+NoIuvaqNhzlF+OMbAft/ylWWKCH4WUP+FKG+1LXM7ud7AA3MMbGSBxHL0/WK/INa7MB56xZKMqqyvvLLQHFTQUROJjkgkzsumdOgwZTRgIFnAZ4+vOX3/1Rtt3mAs3vdoJhL4GuKUYCnEHt908eKkWEVEs7eMk5SdSRtIsbaXO2s0dtADwg==
  template:
    metadata:
      creationTimestamp: null
      name: regcred
      namespace: default
    type: kubernetes.io/dockerconfigjson

Now that we have our sealed registry secret, we can deploy it into our cluster.

kubectl apply -f regcred-sealed.yaml

Now we can use it to pull images from our private registry.

containers:
  imagePullSecrets:
    - name: regcred

Full example #

Here is a full deployment example using all the secrets we have created.

  • regcred
  • mounting a secret
  • envFrom secret
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    service: myservice
  name: myservice
  namespace: mynamespace
spec:
  replicas: 1
  selector:
    matchLabels:
      service: myservice
  strategy:
    type: Recreate
  template:
    metadata:
      creationTimestamp: null
      labels:
        service: myservice
    spec:
      containers:
        - envFrom:
            - secretRef:
                name: mysecret
          env:
            - name: foo
              value: bar
          image: private-registry.io/myimage:1.0.0
          name: myimage
          ports:
            - containerPort: 5000
              protocol: TCP
          resources: {}
          volumeMounts:
            - mountPath: /mysecret
              name: mysecret
      restartPolicy: Always
      volumes:
        - name: mysecret
          secret:
            secretName: mysecret
      imagePullSecrets:
        - name: regcred

Downside #

Now the main downside I see with kubeseal is that it does not provide a way to store your secrets in a way that you can access outside of your cluster. So you need to make sure that you have another solution in place to store your secrets so that you still have them if you ever were to take the cluster down or move from k8s to something else.

Overall the likelyhood of you loosing a production cluster is pretty low, so maybe it’s ok to just trust it depending on what the secrets are. Especially for things you control and can rotate anyways its fine.

Writing

kubernetes 6 months in

I stumbled into kubernetes December 2023 when I was looking for a better way to self host applications. I was looking for something that didn’t require logging into a server and building and deploying like a cave man. I wanted a smoother experience than docker compose was giving me. https://waylonwalker.com/looking-for…

Link

Link

Note

Some Git poll results Some Git poll results Julia Evans · jvns.ca great poll of git questions poll: did you know that in a git merge conflict, the order of the code is different when you do a merge/rebase? merge: <<<<<<< HEAD YOUR CODE OTHER BRANCH’S CODE c694cf8aabe rebase: «««< HEAD OTHER BRANCH’S CODE YOUR CODE d945…

Note

External Link sealed-secrets.netlify.app kubeseal is a pretty simple to get started with way to manage secrets such that they can be stored in a git repo and be picked up by your continuous delivery service. Sealed Secrets provides declarative Kubernetes Secret Management in a secure way. Since the Sealed Secrets are e…

Note

Great episode covering a seemingly simple topic. What I really benefitted from was hearing all the different use cases, from logging, debugging, to a/b testing, caching, and auth. I hadn’t even thought of it being applied to a router. I thought of it being applied for an entire application. This seems very useful for t…

Note

![[None]] I’ve been using these decorators to modify the behavior of specific routes. It will do things like 404 admin only routes in a way that looks just like fastapi ’s default, or only allow certain roles into the route, or redirect unauthenticated users to login. After listening to yesterday’s syntaxfm I’m now rea…

Note

Mar 18 – Mar 24, 20242 years ago

Featured this weekLink

Link

I recently discovered AnyText by tyxsspa, and it’s truly impressive. Official implementation code of the paper <AnyText: Multilingual Visual Text Generation And Editing>

Note

Note

@cassidoo) on X — I MADE AN APP ✨" loading="lazy"> Cassidy ( @cassidoo) on X I MADE AN APP ✨ X (formerly Twitter) · twitter.com Damn are one time paid and have it apps making a comeback? Seems like the perfect thing to have someone else automate and not pay a subscription for. Genius Idea Cassidy!! Now what do you call…

Note

Note

Joining the split keyboards club: a Moonlander story | Carlos Becker This post will describe my experience with a couple of firsts: carlosbecker.com I switched from a 60% vortex pok3r to a 40% corne June, 2021. I can relate to a lot of what Carlos talks about here. I think going from 60%-40% made my journey harder than…

Note

My workflow, part 1 | Carlos Becker I keep getting asked how my setup works, how I use tmux and nvim over ssh… all that good stuff. carlosbecker.com Carlos has a pretty sick setup here, I can relate to mostly, cept the macos part. My main critique is that I don’t think he gave window managers much chance on linux, and…

Note

Mar 11 – Mar 17, 20242 years ago

Featured this weekLink

I’m really excited about full-stack-fastapi-template, an amazing project by fastapi. It’s worth exploring! Full stack, modern web application template. Using FastAPI, React, SQLModel, PostgreSQL, Docker, GitHub Actions, automatic HTTPS and more.

Link

I came across puter from HeyPuter, and it’s packed with great features and ideas. 🌐 The Internet OS! Free, Open-Source, and Self-Hostable.

Link

Note

Note

Note

Note

Mar 4 – Mar 10, 20242 years ago

Featured this weekLink

Link

If you’re into interesting projects, don’t miss out on datasette-litestream, created by datasette. Datasette plugin for streaming SQLite database backups to S3, using Litestream!

Link

Link

I came across minio from minio, and it’s packed with great features and ideas. MinIO is a high-performance, S3 compatible object store, open sourced under GNU AGPLv3 license.

Note

![[None]] I figured out the killer combination for python lsp servers, ruff and jedi! ruff does all of the diagnostics and formatting, then jedi handles all the code objects like go to definition and go to reference.

Note

Client Challenge pypi.org Underrated python library to on board ruff, or just use it on a project where its not the norm. ruff claims that its 99.9% compatible with black and when you read through the known differences they are clearly edge case bugs in black. See this page for more about the comparison to black https:…

Note

![[None]] First I need to fetch my thoughts from the api, and put it in a local sqlite database using. Now that I have my posts in a local sqlite database I can use to enable full text search and populate the full text search on the post table using the title message and tags columns as search. Now I am ready to search…

Note

External Link screenshots.waylonwalker.com This is the best tree I have ever built in minecraft. It took at least 4 stacks of logs and leaves despite what it looks like. It is placed where Welscraft’s island in the hermitcraft season 10 seed, but on our own server we call lonecraft. We started this server a few weeks a…

Note

Formatting codes – Minecraft Wiki Formatting codes (also known as color codes) add color and modifications to text in-game. Minecraft Wiki · minecraft.wiki Minecraft MOTD and server names have formatting codes so that you can get colors, bold, underlined, italics, in your message of the day or server name. See the arti…

Note

Note

Go is feeling more and more like something I could throw in my tool belt as a python dev. I really like that it’s garbage collected and has great error management. I am just not sure how to work it in without it being the main thing. The thing that is so cool is the ability to ship tiny pre-compiled binaries that just…

Note

Note