Archive

All published posts

2620 posts latest post 2026-09-24 simple view
Publishing rhythm
Sep 2026 | 29 posts
GitHub - johanhaleby/kubetail: Bash script to tail Kubernetes logs from multiple pods at the same time Bash script to tail Kubernetes logs from multiple pods at the same time - johanhaleby/kubetail GitHub · github.com Kubetail is a pretty sick bash script that allows you to tail logs for multiple pods in one stream. Very handy when you have more than one replica running. Now with kubetail I can tail all the logs for every shot-wayl-one pod in the shot namespace.
I am converting my docker compose env secrets over to k8s secrets. This guide was clear and to the point how I can replicate this exact workflow. First set the secret, the easiest way is to use kubectl wtih –from-literal because it automatically base64 encodes for you. If you don’t use the you will have to base64 encode it. Once you have your secret deployed, you have to update the container spec in your deployment manifest to get the valueFrom secretKeyRef.
waylon walker (@_WaylonWalker) on X Which is more complicated X (formerly Twitter) · twitter.com Wow, shocked at these results. All this time I’ve been told and believed that k8s is incredibly hard, and you need a $1M problem before you think about it because it will take a $1M team to maintain it. So far my experience has been good, and I definitely do not have a $1M problem in my homelab.
Wes Bos (@wesbos) on X 🔥 The stale-while-revalidate header is suuuuuuper handy for striking a balance between fast loads and and frequently changed content. Here I am using it to instantly deliver an OG image that t… X (formerly Twitter) · twitter.com Wes has some of the coolest OG images i’ve ever seen. Here he talks about how to enable cache configuration so that its constantly updating the cache without the user waiting for the image to be created.
</> htmx ~ Locality of Behaviour (LoB) Carson Gross explores the Locality of Behaviour (LoB) principle, which emphasizes making the behavior of code units obvious on inspection to enhance maintainability. He discusses the tradeoffs betw... htmx.org Interesting principle here. What a great example, If I’m looking at the second jQuery example, I have to dig into dev tools or make some assumtions that this team uses jQuery, and selects by id, therefore I can grep for. Consider two different implementations of an AJAX request in HTML, the first in htmx:
External Link thoughts.waylonwalker.com I was looking to add running kubernetes jobs to a python cli I am creating, and I found this solution, mostly thanks to and my loose understanding of what the yaml syntax is supposed to look like for a kubernetes job. This will let me create a job in the cluster, choose the image that runs, the command that is called, and how long until the job expires and is cleaned up. While the job still exists I can go in and look at the logs, but once its ttl has expired they are gone.
How to kill process based on the port number in Linux Learn to kill a process by port in Linux using fuser, lsof, and ss commands. Essential for system admins managing network processes efficiently. LinuxConfig · linuxconfig.org I’ve often struggled to find and kill a process using a certain port on archlinux. Mainly becuase most guides use netstat rather than. Here is how I just killed the process using port 5000 using. You can also get information about the process by running
Just starred just by casey. It’s an exciting project with a lot to offer. 🤖 Just a command runner
GitHub - casey/just: 🤖 Just a command runner 🤖 Just a command runner. Contribute to casey/just development by creating an account on GitHub. GitHub · github.com I think just, might just be the thing I have been looking for. I’ve been looking for some ci/cd that I can host myself, but everything looks pretty big, so for now I am going to use just as my task runner. I installed with installer. I set up my devtainer builds with just. Here is my, yes you just need the cli and a file named.
Quick-Start Guide | K3s This guide will help you quickly launch a cluster with default options. Make sure your nodes meet the requirements before proceeding. docs.k3s.io I recently spun up k3s in my homelab. I’m trying to offload some work off of my free tier fly.io app in order to keep it free tier without crashing. My main hiccup so far was the machine I am running on runs zfs on root, and it would not start the master node. Rather than figuring out how to make zfs play nice I just pointed k3s to a drive that is not zfs.
Litestar: Effortlessly Build Performant APIs We all know about Flask and Django. And of course FastAPI made a huge splash when it came on the scene a few years ago. But new web frameworks are being created all the time. And they have these ea... talkpython.fm Litestar is an interesting api framework similar to fastpi, that I am interested to check out to see if it fits into some project scope. It sounds like it comes with a lot more batteries included for things like auth, but does not have hard opinions like django. At this point I’m not jumping off of fastapi, but its something I want to try.
Ollama Ollama is the easiest way to automate your work using open models, while keeping your data safe. ollama.ai ollama is the easiest to get going local llm tool that I have tried, and seems to be crazy fast. It feels faster than chat gpt, which has not been the experience I have had previously with running llm’s on my hardware.
gen.nvim by David-Kunz is a game-changer in its space. Excited to see how it evolves. Neovim plugin to generate text using LLMs with customizable prompts
GitHub - sysid/sse-starlette Contribute to sysid/sse-starlette development by creating an account on GitHub. GitHub · github.com sse- FastAPI.">starlette provides server sent events for startlette and FastApi. I’m evaluating for use with htmx. Installation: # Usage: #
Change Autocomplete Styles in WebKit Browsers | CSS-Tricks We got a nice tip from Lydia Dugger via email with a method for changing the styles that WebKit browsers apply to form fields that have been autocompleted. CSS-Tricks · css-tricks.com All the hover, select, autofil, focus combinations have left me confused on how to consistently get my form elements styled in dark mode This snippet from CSS tricks has fixed all the different states for me to give me full control.
GitHub - florimondmanca/arel: Lightweight browser hot reload for Python ASGI web apps Lightweight browser hot reload for Python ASGI web apps - florimondmanca/arel GitHub · github.com arel is a “Lightweight browser hot reload for Python ASGI web apps” I just implemented this on my thoughts website using fastapi, and it’s incredibly fast and lightweight. There just two lines of js that make a web socket connection back to the backend that watches for changes. When in development mode, this snippet gets injected directly on the page and does a refresh when arel detects a change.
Bob Belderbos (@bbelderbos) on X Forget Python for a sec, here's how Vim helped me out today … 💪 📈 Ever felt like you needed a quick string replacement without diving into a script? Here's a Vim trick I just used … I w… X (formerly Twitter) · twitter.com I need to learn regex capture groups better. This is so dang powerful. I really like the \v that bob uses here, it really does cut down on the terseness of all the special characters. I wanted to replace all occurrences of: name,[email protected],0,171,,2023-09-21 With: name,[email protected] Easy to do with Python, but what about a bit of > regex in Vim?:%s/\v([,]+,[,]+),.*/\1/
Open source, not open contribution with Ben Johnson (Changelog Interviews #433) This week we're talking with Ben Johnson. Ben is known for his work on BoltDB, his work in open source, and as a freelance Go developer. Late January when Ben open sourced his newest project Litest... Changelog · changelog.com Ben Johnson was on the Changelog a few years back covering his work on litestream, and talks about why he chose to go open source, but not open contribution. You should have a good reason to move off of sqlite.

jpillora/installer is the install script generator I have been looking for. It downloads binaries for your machine from GitHub releases and unzips them for you. It grabs the latest release, so you can easily update them. I have tried scripting these installs in the past and struggled to consistently get the latest version for every package and unpack it correctly.

Also these pre-compiled binaries install rediculously fast compared to building them from source.

Check out some example links.

opening in a browser will show metadata

https://i.jpillora.com/serve

If you pass in script=true it will instead return the install script as it would by default through curl.

https://i.jpillora.com/serve?script=true

Use it to install neovim #

All you need to do to generate an install script is to pass in the GitHub repo slug with the org.

curl https://i.jpillora.com/neovim/neovim | bash

The shell script that it generates for neovim looks like this.

#!/bin/bash
if [ "$DEBUG" == "1" ]; then
    set -x
fi
TMP_DIR=$(mktemp -d -t jpillora-installer-XXXXXXXXXX)
function cleanup {
    rm -rf $TMP_DIR > /dev/null
}
function fail {
    cleanup
    msg=$1
    echo "============"
    echo "Error: $msg" 1>&2
    exit 1
}
function install {
    #settings
    USER="neovim"
    PROG="neovim"
    ASPROG=""
    MOVE="false"
    RELEASE="stable"
    INSECURE="false"
    OUT_DIR="$(pwd)"
    GH="https://github.com"
    #bash check
    [ ! "$BASH_VERSION" ] && fail "Please use bash instead"
    [ ! -d $OUT_DIR ] && fail "output directory missing: $OUT_DIR"
    #dependency check, assume we are a standard POISX machine
    which find > /dev/null || fail "find not installed"
    which xargs > /dev/null || fail "xargs not installed"
    which sort > /dev/null || fail "sort not installed"
    which tail > /dev/null || fail "tail not installed"
    which cut > /dev/null || fail "cut not installed"
    which du > /dev/null || fail "du not installed"
    #choose an HTTP client
    GET=""
    if which curl > /dev/null; then
        GET="curl"
        if [[ $INSECURE = "true" ]]; then GET="$GET --insecure"; fi
        GET="$GET --fail -# -L"
    elif which wget > /dev/null; then
        GET="wget"
        if [[ $INSECURE = "true" ]]; then GET="$GET --no-check-certificate"; fi
        GET="$GET -qO-"
    else
        fail "neither wget/curl are installed"
    fi
    #debug HTTP
    if [ "$DEBUG" == "1" ]; then
        GET="$GET -v"
    fi
    #optional auth to install from private repos
    #NOTE: this also needs to be set on your instance of installer
    AUTH="${GITHUB_TOKEN}"
    if [ ! -z "$AUTH" ]; then
        GET="$GET -H 'Authorization: $AUTH'"
    fi
    #find OS #TODO BSDs and other posixs
    case `uname -s` in
    Darwin) OS="darwin";;
    Linux) OS="linux";;
    *) fail "unknown os: $(uname -s)";;
    esac
    #find ARCH
    if uname -m | grep -E '(arm|arch)64' > /dev/null; then
        ARCH="arm64"

        # no m1 assets. if on mac arm64, rosetta allows fallback to amd64
        if [[ $OS = "darwin" ]]; then
            ARCH="amd64"
        fi

    elif uname -m | grep 64 > /dev/null; then
        ARCH="amd64"
    elif uname -m | grep arm > /dev/null; then
        ARCH="arm" #TODO armv6/v7
    elif uname -m | grep 386 > /dev/null; then
        ARCH="386"
    else
        fail "unknown arch: $(uname -m)"
    fi
    #choose from asset list
    URL=""
    FTYPE=""
    case "${OS}_${ARCH}" in
    "linux_amd64")
        URL="https://github.com/neovim/neovim/releases/download/stable/nvim-linux64.tar.gz"
        FTYPE=".tar.gz"
        ;;
    "darwin_amd64")
        URL="https://github.com/neovim/neovim/releases/download/stable/nvim-macos.tar.gz"
        FTYPE=".tar.gz"
        ;;
    *) fail "No asset for platform ${OS}-${ARCH}";;
    esac
    #got URL! download it...
    echo -n "Downloading"
    echo -n " $USER/$PROG"
    if [ ! -z "$RELEASE" ]; then
        echo -n " $RELEASE"
    fi
    if [ ! -z "$ASPROG" ]; then
        echo -n " as $ASPROG"
    fi
    echo -n " (${OS}/${ARCH})"

    echo "....."

    #enter tempdir
    mkdir -p $TMP_DIR
    cd $TMP_DIR
    if [[ $FTYPE = ".gz" ]]; then
        which gzip > /dev/null || fail "gzip is not installed"
        bash -c "$GET $URL" | gzip -d - > $PROG || fail "download failed"
    elif [[ $FTYPE = ".tar.bz" ]] || [[ $FTYPE = ".tar.bz2" ]]; then
        which tar > /dev/null || fail "tar is not installed"
        which bzip2 > /dev/null || fail "bzip2 is not installed"
        bash -c "$GET $URL" | tar jxf - || fail "download failed"
    elif [[ $FTYPE = ".tar.gz" ]] || [[ $FTYPE = ".tgz" ]]; then
        which tar > /dev/null || fail "tar is not installed"
        which gzip > /dev/null || fail "gzip is not installed"
        bash -c "$GET $URL" | tar zxf - || fail "download failed"
    elif [[ $FTYPE = ".zip" ]]; then
        which unzip > /dev/null || fail "unzip is not installed"
        bash -c "$GET $URL" > tmp.zip || fail "download failed"
        unzip -o -qq tmp.zip || fail "unzip failed"
        rm tmp.zip || fail "cleanup failed"
    elif [[ $FTYPE = ".bin" ]]; then
        bash -c "$GET $URL" > "neovim_${OS}_${ARCH}" || fail "download failed"
    else
        fail "unknown file type: $FTYPE"
    fi
    #search subtree largest file (bin)
    TMP_BIN=$(find . -type f | xargs du | sort -n | tail -n 1 | cut -f 2)
    if [ ! -f "$TMP_BIN" ]; then
        fail "could not find find binary (largest file)"
    fi
    #ensure its larger than 1MB
    #TODO linux=elf/darwin=macho file detection?
    if [[ $(du -m $TMP_BIN | cut -f1) -lt 1 ]]; then
        fail "no binary found ($TMP_BIN is not larger than 1MB)"
    fi
    #move into PATH or cwd
    chmod +x $TMP_BIN || fail "chmod +x failed"
    DEST="$OUT_DIR/$PROG"
    if [ ! -z "$ASPROG" ]; then
        DEST="$OUT_DIR/$ASPROG"
    fi
    #move without sudo
    OUT=$(mv $TMP_BIN $DEST 2>&1)
    STATUS=$?
    # failed and string contains "Permission denied"
    if [ $STATUS -ne 0 ]; then
        if [[ $OUT =~ "Permission denied" ]]; then
            echo "mv with sudo..."
            sudo mv $TMP_BIN $DEST || fail "sudo mv failed"
        else
            fail "mv failed ($OUT)"
        fi
    fi
    echo "Downloaded to $DEST"
    #done
    cleanup
}
install

Self Host Your Own #

I’d reccomend self hosting your own. This way you know that it’s consistent and unlikely to change in a way that breaks your use.

curl -s https://i.jpillora.com/installer | bash

Repos I am using installer for #

Here are the repos I am using installer for.

atuinsh/atuin
benbjohnson/litestream
bootandy/dust
BurntSushi/ripgrep
chmln/sd
cjbassi/ytop
dalance/procs
dbrgn/tealdeer
ducaale/xh
go-task/task
imsnif/bandwhich
imsnif/diskonaut
kovidgoyal/kitty
mgdm/htmlq
neovim/neovim
ogham/dog
ogham/exa
pemistahl/grex
sharkdp/bat
sharkdp/fd
sharkdp/pastel
sirwart/ripsecrets
starship/starship
topgrade-rs/topgrade
zellij-org/zellij

I wanted to host some static files through fastapi. Typical use cases for this might be some static web content like html/css/js. It could also be images or some data that doesn’t need dynamically rendered.

From the Docs #

The docs cover how to host static files, and give this solution that is built into fastapi.

https://fastapi.tiangolo.com/tutorial/static-files/

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

Authenticated Static Files #

Thanks to #858.

OscartGiles posted this solution to add authentication to static files. I tried this out on my thoughts and it worked flawlessly.

import typing
from pathlib import Path
import secrets

from fastapi import FastAPI, Request, HTTPException, status
from fastapi.staticfiles import StaticFiles
from fastapi.security import HTTPBasic, HTTPBasicCredentials


PathLike = typing.Union[str, "os.PathLike[str]"]
app = FastAPI()
security = HTTPBasic()


async def verify_username(request: Request) -> HTTPBasicCredentials:

    credentials = await security(request)

    correct_username = secrets.compare_digest(credentials.username, "user")
    correct_password = secrets.compare_digest(credentials.password, "password")
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect email or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username


class AuthStaticFiles(StaticFiles):
    def __init__(self, *args, **kwargs) -> None:

        super().__init__(*args, **kwargs)

    async def __call__(self, scope, receive, send) -> None:

        assert scope["type"] == "http"

        request = Request(scope, receive)
        await verify_username(request)
        await super().__call__(scope, receive, send)


app.mount(
    "/static",
    AuthStaticFiles(directory=Path(__file__).parent / "static"),
    name="static",
)

If you want both then, all you have to do is mount AuthStaticFiles to a different route. Now you can have private, or paid content behind /restricted.

app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount(
    "/restricted",
    AuthStaticFiles(directory=Path(__file__).parent / "restricted"),
    name="restricted"
)

I recently se tup minio object storage in my homelab for litestream sqlite backups. The litestream quickstart made it easy to get everything up and running on localhost, but I hit a wall when dns was involved to pull it from a different machine.

Here is what I got to work #

First I had to configure the Key ID and Secret Access Key generated in the minio ui.

❯ aws configure
AWS Access Key ID [****************VZnD]:
AWS Secret Access Key [****************xAm8]:
Default region name [us-east-1]:
Default output format [None]:

Then set the the s3 signature_version to s3v4.

aws configure set default.s3.signature_version s3v4

Now when I have minio running on https://my-minio-endpoint.com I can use the aws cli to access the bucket.

Note that https://my-minio-endpoint.com resolves to the bucket endpoint (default 9000) not the ui (default 9001).

aws --endpoint-url https://my-minio-endpoint.com s3 ls my_bucket

Now Configuring Litestream #

Litestream also accepts the endpoint argument via config. I could not get it to work just with the ui.

Note the aws configure step above is not required for litestream, only the aws cli.

dbs:
  - path: /path/to/database.db
    replicas:
      - url: s3://my_bucket/
        endpoint: https://my-minio-endpoint.com
        region: us-east-1
        access-key-id: ****************VZnD
        secret-access-key: ************************************xAm8

Now run a litestream replication.

litestream replicate -config litestream.yml
# or put the config in /etc/litestream.yml and just run replicate
litestream replicate