Nuggets & TIL

Small pieces of config, conventions, and TILs (today-I-learned moments) that I thought were worth writing down.

Commit messages should complete a sentence

A good commit message completes the sentence: “Applying this change will…”

Not:

Properties of a good continuous delivery (CD) pipeline

Unlock account after too many failed password attempts (Arch Linux)

Arch Linux uses pam_faillock to temporarily lock an account after repeated failed logins. The symptom: the correct password still gets rejected.

Reset the counter with:

faillock --user <username> --reset

If you’re locked out of your own session entirely, switch to another TTY (Ctrl+Alt+F2), log in as root, and run it from there.

To see the current state before resetting:

faillock --user <username>

The thresholds (number of attempts, lockout duration) are configured in /etc/security/faillock.conf. Usually I increase it the first time this happens to me…

SSH agent inside devcontainers

SSH keys should be managed by an agent, not loaded directly. The typical failure mode: keys work on the host, but disappear inside a devcontainer.

The fix is two-part. First, start the agent in your login shell profile (~/.profile or ~/.bash_profile, not ~/.bashrc — that one only runs in interactive non-login shells and won’t be sourced in many devcontainer scenarios):

# ~/.profile
if [ -z "$SSH_AUTH_SOCK" ]; then
    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/id_ed25519
fi

Second, forward the agent socket into the container. For VS Code devcontainers this is automatic when the Dev Containers extension detects a running agent. If it doesn’t pick it up, make sure SSH_AUTH_SOCK is exported before the container starts. On Linux / WSL you can verify with:

echo $SSH_AUTH_SOCK   # should print a socket path, not be empty
ssh-add -l            # should list your loaded keys

If both look right on the host but the container still can’t see them, check whether the socket path is being mounted. The extension mounts it under $SSH_AUTH_SOCK inside the container by default.

To avoid the agent disappearing in the first place, you can also start it in your profile and save the environment variables to a file. This way, if the agent is already running, you won’t start a new one and lose your loaded keys:

if [ -z "$SSH_AUTH_SOCK" ]; then
    # Check for a currently running instance of the agent
    RUNNING_AGENT="`ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'`"
    if [ "$RUNNING_AGENT" = "0" ]; then
        # Launch a new instance of the agent
        ssh-agent -s &> $HOME/.ssh/ssh-agent
    fi
    eval `cat $HOME/.ssh/ssh-agent` > /dev/null
    ssh-add 2> /dev/null
fi

Conventional commits in lazygit

Lazygit lets you bind custom commands to keys. A short menu-driven prompt makes it easy to stay consistent with conventional commits without typing the prefix every time.

In ~/.config/lazygit/config.yml:

customCommands:
  - key: "c"
    context: "files"
    description: "Conventional commit"
    loadingText: "Committing..."
    prompts:
      - type: "menu"
        title: "Commit type"
        key: "Type"
        options:
          - name: "feat"
            description: "A new feature"
            value: "feat"
          - name: "fix"
            description: "A bug fix"
            value: "fix"
          - name: "chore"
            description: "Other changes that don't modify src or test files"
            value: "chore"
          - name: "docs"
            description: "Documentation only changes"
            value: "docs"
          - name: "refactor"
            description: "Code change that neither fixes a bug nor adds a feature"
            value: "refactor"
          - name: "style"
            description: "Formatting, whitespace, no code change"
            value: "style"
          - name: "perf"
            description: "Performance improvement"
            value: "perf"
          - name: "test"
            description: "Adding or fixing tests"
            value: "test"
          - name: "build"
            description: "Build system or external dependencies"
            value: "build"
          - name: "ci"
            description: "CI configuration"
            value: "ci"
          - name: "revert"
            description: "Revert a previous commit"
            value: "revert"
      - type: "input"
        title: "Scope (leave empty to skip)"
        key: "Scope"
      - type: "input"
        title: "Commit message"
        key: "Message"
    command: 'git commit -m "{{.Form.Type}}{{ if .Form.Scope }}({{ .Form.Scope }}){{ end }}: {{ .Form.Message }}"'

  - key: "C"
    context: "files"
    description: "Quick commit (plain message)"
    loadingText: "Committing..."
    prompts:
      - type: "input"
        title: "Commit message"
        key: "Message"
    command: 'git commit -m "{{ .Form.Message }}"'

The custom command overrides the built-in c binding (which normally opens the plain commit editor). The original behaviour is preserved on capital C here as a fallback for the cases where the prompt-driven flow gets in the way.

pkill -SIGUSR2 to reload a running program

I often use these commands to reload configs:

pkill -SIGUSR2 waybar
pkill -SIGUSR2 swaync

When pkill -SIGUSR2 waybar instantly reloads waybar’s config without restarting the process, it’s tempting to think SIGUSR2 “means” reload but it is actually just a signal that waybar happens to listen for. Signals are notifications from the kernel to a process and it can decide how to react to them, except for SIGKILL and SIGSTOP which can’t be overridden. SIGUSR1 and SIGUSR2 are deliberately undefined by POSIX but the default action is to terminate the process if it receives them and doesn’t have a handler registered. However, there is a soft convention has emerged across Wayland status bars, notification daemons, and launchers:

Window manager vs. session manager vs. display server vs. display manager

I find these fairly confusing, so just to summarize the layers:

Types of architecture

I usually don’t really care much about titles that people hold, but I deal with a lot of different architects so I made myself this little cheat sheet to try to mentally file what they will be concerned about. (In practice the roles are so varied that I always just ask them anyways what they’re actually responsible for.)

Stale paru source cache fails the validity check

Sometimes an AUR package update fails like this even though nothing is wrong with the package:

==> Retrieving sources...
  -> Found xxx-linux.tar.gz
==> Validating source files with sha256sums...
    xxx-linux.tar.gz ... FAILED
==> ERROR: One or more files did not pass the validity check!

The hint is the -> Found xxx-linux.tar.gz line. “Found” means paru reused a cached download instead of fetching a fresh one. If the source filename doesn’t contain the version (like here), the cached tarball from the previous release gets checked against the new release’s checksum and fails.

Although there are better ways that just delete that one cached package, the easier way that I can actually remember is to just use paru -Sc to clear all the cache.

Definition of “legacy code”

Legacy code is “code you’re not comfortable changing” or “valuable code that you’re afraid to change.” (from Nicolas Carlo)

Microservices are about modularity, and modularity has dimensions

Microservices are independently deployable services that work together.

They often get sold as a scalability story, but really they are about modularity: well-defined boundaries with high cohesion inside and loose coupling between.

However, there are also different dimensions of modularity that we can optimize for:

A good microservice architecture should give you both of the above. But you pay for it with a lot of technical and operational overhead. A modular monolith deliberately focuses on the first dimension (have clean modules and boundaries in your system that you could also split out separately) but you can deploy everything together. It is often the better choice for most teams, especially early on, because you get the benefits of clear boundaries without the additional complexity.