• Weeknotes - 2 million containers, thermometer firmware

    This is the first post of a maybe-once-a-week series of things that I found on the internet that may be worth a look.


    Hashicorp - The 2 Million Container Challenge

    fasterthanli.me - A half-hour to learn Rust

    Julia Evans - Docker Compose: a nice way to set up a dev environment

    Custom firmware for the Xiaomi Thermometer LYWSD03MMC https://github.com/pvvx/ATC_MiThermometer

  • Make a DaemonSet schedule a pod on the master node(s)

    Who hasn't been building a small Pi-based Kubernetes cluster at home?

    Well, on mine I wanted to deploy a DaemonSet with node_exporter for my external Prometheus service to scrape metrics of each node in the cluster, so I wrote up a DaemonSet manifest and applied it to my cluster. On first glance everything seemed happy, but quickly I noticed that the master node was not running a node-exporter pod!

    A quick Stack Overflow search led me to this answer and informed me that since Kubernetes 1.6 you have to add a toleration to the Pod spec section of your DaemonSet manifest:

    - key: node-role.kubernetes.io/master
    effect: NoSchedule

    I added this, and my master node was now running a node-exporter pod—just as I wished.

  • Using Vim to record a maintenance event timeline

    Vim can do lots of things

    After-hours maintenance is something my team and I encounter frequently. Whether it's customer turn-ups, circuit migrations, or firmware upgrades—we get used to the 10pm to early morning working hours!

    Often, we like to record a rough timeline of events during the maintenance period in order to have timestamped context for re-tracing our steps and/or investigating persistent issues before/during/after the maintenance period. Historically I've manually inserted timestamps as we never keep too granular of a timeline. However, this last go-around I decided to dig a little into Vim and look into how to automate the insertion of timestamps whenever I start a new line in the editor.

    Insert mode maps

    Vim/vi is nearly limitless in its customization. One of the configurable properties is key mappings. Key mappings allow the creation of a shortcuts for the purpose of repeating a sequence of keys or commands.

    In my use case, I want to map the automatic input of the formatted, current time using Vim's internal strftime() function whenever we drop down to a new line in the editor. The mapping below is what I came up with:

    :imap <buffer> <CR> <CR><C-R>=strftime("%Y-%m-%d %H:%M:%S :: ")<CR>
    • imap tells Vim that this is an insert mode map
    • <buffer> tells Vim to only consider the mapping in the buffer in which it is defined
    • The first <CR> is the left-hand-side of the map command, which defines the keys used to activate the shortcut. <CR> and <Enter> are synomymous
    • <C-R> tells vim to insert the following expression at the cursor
    • =strftime("%Y-%m-%d %H:%M:%S :: ") is our strftime() function to grab the current date and time in the preferred format
    • The final <CR> executes the expression

    If the above is entered as a Vim command, a timestamp will be inserted at the beginning of the line every time Enter is pressed while in insert mode.

    Make it a Vim filetype

    Now that we have the functionality we want, the next step is to automate the key mapping when it's suitable.

    First, enable the filetype plugin in your .vimrc file:

    filetype plugin on

    Create the following directories:

    • ~/.vim/ftdetect
    • ~/.vim/ftplugin

    In my example, I'm going to define a new filetype called toe (timeline of events) with the extension of .toe.

    Let's define our filetype detection file:


    autocmd BufNewFile,BufRead *.toe set filetype=toe

    This sets the Vim filetype to toe when the filename ends in .toe.

    Lastly, we define the command(s) we want active when the filetype is toe:


    imap <buffer> <CR> <CR><C-R>=strftime("%Y-%m-%d %H:%M:%S :: ")<CR>

    Now, whenever you manually set the filetype to toe or open a file that ends in .toe, Vim will know to insert a timestamp at the beginning of each new line.

    Originally, I wanted to make a quick CLI tool in Go to replicate this behavior, but I'm glad that I went down the route of learning a bit more about Vim/vi.

  • Updating my profile README with GitHub Actions

    Woah cool

    Having heard of GitHub's new profile README feature—and seeing many fun and clever takes on its functionality—I decided to take a shot at simple README showing something I like, backed up by a simple GitHub Action that keeps the content updated throughout the day. Having used self-hosted CI/CD tools like Drone to automatically deploy configurations and code in the past, this seemed like an easy introduction to GitHub's own take on CI/CD.

    For the content I chose a framegrab from my webcam at Gibraltar Peak here in Santa Barbara. Some friends and I rent some tower space on this peak for a small community internet project and have placed a cheap IP camera facing west to catch the daily sunset—and peek above the clouds when the marine layer makes an appearance.

    Create a jpeg from the RTSP stream

    On a server at the peak I have ffmpeg pulling in the RTSP feed and grabbing a single frame, resizing it, and saving it to a publicly-accessibly URL:

    /usr/local/bin/ffmpeg -y -i "rtsp://<camera_ip_and_port>/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif" -vframes 1 -s 1280x720 /<web_root>/m/frame.jpg

    An entry in the crontab runs the above script every minute.


    This is near as simple as a README.md can get:

    `Updated-every-five-minutes frame from my webcam on Gibraltar Peak in Santa Barbara, California.`

    Note that I've linked to the raw repository URL of frame.jpg.

    The GitHub action

    Coming from using Drone and its YAML syntax, defining my new action felt pretty familiar.

    Using the supported schedule trigger, the action below performs the following every five minutes:

    • Checks out the repository using GitHub's first-party checkout action
    • Grabs the latest framegrab from our web server
    • If git diff detects a change, the new frame.jpg is committed and pushed to the master branch
    name: frame-update

    - cron: '*/5 * * * *'

    runs-on: ubuntu-latest
    - name: Checkout
    uses: actions/checkout@v2

    - name: Grab latest stream frame
    run: |-
    wget https://internetmountain.org/m/frame.jpg -O frame.jpg

    - name: Commit and push
    run: |-
    git diff
    git config --global user.email "readme-bot@your-domain.org"
    git config --global user.name "README-bot"
    git diff --quiet || (git add frame.jpg && git commit -m "Update stream frame")
    git push

    Nice and happy

    I came away from this little tangent of a project amazed by how extensible and capable GitHub actions are. Considering that public repositories get unlimited minutes of actions, I will be spending many spare brain cycles on new ideas of how to make Actions do something fun and useful, even if slightly unnecessary...

    My GitHub profile repository is here:


  • How to send a single UDP packet with Netcat

    At work today I needed this command to test UDP traffic to a particular endpoint, and thought I'd leave it written down here!

    This netcat command sends a single UDP packet with the string "foo" to a particular IPv4 address on a particular UDP port:

    $ echo -n “foo” | netcat -u -w1 <host_ip> <port>
  • Using Ansible to provision LXC containers

    Since all of us have a bit of extra time lately I grabbed a copy of Jeff Geerling's Ansible for DevOps to finally tackle the project to translate my many, many bash scripts into something for flexible and idempotent.

    Ever since I began moving a lot of my cloud-hosted projects and applications to my own hardware in a colocation facility, I've spent a lot of time spinning up linux containers on Proxmox and running bash scripts to get them into a state that's useful for me. This can get tedious and time consuming!

    My first project with Ansible was to not only streamline the creating of linux containers on my Proxmox hypervisor, but to provision them with the base configuration they need—and to subscribe them to my FreeIPA domain for central authentication.

    Besides the included README, you can find the entire playbook on GitHub.


    ansible-pve-lxc is an Ansible playbook and set of roles for provisioning and configuring LXC containers in a Proxmox virtual environment, and subsequently subscribing them to a FreeIPA domain.

    Define inventory with container configurations

    This inventory is in the YAML format, but will also work in INI format



    Populate host variables with individual container configurations


    node: 'gold'
    vmid: '110'
    cores: '1'
    memory: '1024'
    swap: '0'
    onboot: true
    network: '{"net0":"name=eth0,ip=,gw=,ip6=dhcp,bridge=vmbr1"}'


    node: 'gold'
    vmid: '111'
    cores: '2'
    memory: '2048'
    swap: '0'
    onboot: true
    network: '{"net0":"name=eth0,ip=,gw=,ip6=dhcp,bridge=vmbr1"}'

    Configure variables

    Define PVE API and LXC default variables


    api_host: 'pve.site.domain'
    api_user: 'root@pam'

    node: 'gold'
    storage: 'local-zfs'
    cores: '1'
    memory: '1024'
    onboot: true
    pubkey: ''
    searchdomain: 'site.domain'
    nameserver: ','
    ostemplate: 'bulk:vztmpl/centos-8-20200402_amd64.tar.gz'
    network: '{"net0":"name=eth0,ip=dhcp,ip6=dhcp,bridge=vmbr1"}'

    Define PVE API password, SSH public key, and IPA admin password


    api_password: '<proxmox_api_password>'
    ssh_pubkey: '<ssh_public_key>'
    ipa_domain: 'ipa.site.domain'
    ipa_realm: 'IPA.SITE.DOMAIN'
    ipa_principal: 'admin'
    ipa_admin_pw: '<ipa_admin_password>'

    Optional (but suggested!): Use ansible-vault to encrypt group_vars/all.yml

    $ nsible-vault encrypt group_vars/all.yml

    Use ansible-pve-lxc

    Optional: Temporarily disable ssh host key checking


    Run playbook

    $ ansible-playbook -i inventory site.yml --ask-become-pass

    Run playbook while asking for the vault password interactively

    $ ansible-playbook -i inventory site.yml --ask-become-pass --ask-vault-pass

    Optional: Re-enable ssh host key checking

  • yeah - CLI tool for looking up OUIs

    A background-goal of mine this last month has been to learn enough of Rust to code something useful that I can use at work. In spare moments since the new year I've been reading "the book"—which has been more than helpful in learning the basics of Rust and what makes it such an interesting, powerful language.

    Fast-forward to today and I'm happy to share the useful something that I managed to make with Rust. Below is the README from yeah's GitHub page:


    yeah is a command-line tool to return the vendor name for a given MAC address. Queries are ran against the IEEE OUI vendor list.


    Supports complete and partial MAC address queries

    Install yeah

    With crates.io:

    $ cargo install yeah

    From source:

    $ cargo install --path /path/to/yeah/repo

    Or, download and run from .zip or .tar.gz in releases

    Use yeah

    Print vendor of given MAC address

    $ yeah <mac>


      -h, --help          Prints help information
      -v, --version       Prints version information