• 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:

    ~/.vim/ftdetect/toe.vim

    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:

    ~/.vim/ftplugin/toe.vim

    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:

    #!/bin/bash
    /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.

    README.md

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

    ![](https://raw.githubusercontent.com/raylas/raylas/master/frame.jpg)
    `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
    
    on:
      push:
      workflow_dispatch:
      schedule:
        - cron: '*/5 * * * *'
    
    jobs:
      update:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/[email protected]
    
          - name: Grab latest stream frame
            uses: wei/[email protected]
            with:
              args: https://internetmountain.org/m/frame.jpg -O frame.jpg
    
          - name: Commit and push
            run: |-
              git diff
              git config --global user.email "[email protected]"
              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:

    https://github.com/raylas/raylas

  • 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

    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

    ---
    
    proxmox:
      hosts:
        pve.site.domain
    
    containers:
      hosts:
        db1.site.domain:
          ansible_host: 10.0.10.125
        app1.site.domain:
          ansible_host: 10.0.10.126

    Populate host variables with individual container configurations

    host_vars/db1.site.domain.yml

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

    host_vars/app1.site.domain.yml

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

    Configure variables

    Define PVE API and LXC default variables

    group_vars/proxmox.yml

    ---
    
    api_host: 'pve.site.domain'
    api_user: '[email protected]'
    
    defaults:
      node: 'gold'
      storage: 'local-zfs'
      cores: '1'
      memory: '1024'
      onboot: true
      pubkey: '{{ ssh_pubkey }}'
      searchdomain: 'site.domain'
      nameserver: '1.1.1.1,8.8.8.8'
      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

    group_vars/all.yml

    ---
    
    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

    $ ansible-vault encrypt group_vars/all.yml

    Use ansible-pve-lxc

    Optional: Temporarily disable ssh host key checking

    $ export ANSIBLE_HOST_KEY_CHECKING=False

    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

    $ unset ANSIBLE_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

    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.

    Functionality

    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>

    Options:

      -h, --help          Prints help information
      -v, --version       Prints version information
  • Enabling unsupported SFP+ transceivers on the Intel X520/Dell R720

    Having recently set up a new-to-me Dell R720 in a colocation provider’s environment—in my quest to “own” the whole stack from metal to service—I ran into an issue with not being able to use generic or Cisco-coded SFP+/SFP trancseivers in the included Intel X520 network card. dmesg kept indicating that the transceiver was unsupported. Because of impatience and other factors, I decided to dig a bit and see what it would take to enable support for good ‘ol grab bag generic SFP+/SFP transceivers.

    I came across this article on the ServeTheHome forums where user NathanA had already dug to the bottom of this issue, and fortunately came out the other side with a solution.

    NathanA discovered—via datasheets, mailing lists, and discussion threads—that a particular bit in a particular byte of the NIC EEPROM needs to be flipped in order to allow any SFP. In the Linux ixgbe driver, the byte in question is referred to as IXGBE_DEVICE_CAPS_ALLOW_ANY_SFP.

    The steps to flip this bit, and thus allowing “unsupported” SFPs, is as follows:

    Note: I am in no way liable for any permanent damaged caused by running the following commands on your Intel NIC!

    Identify current value of offset 0x58

    $ sudo ethtool -e eno1 offset 0x58 length 1
    Offset          Values
    ------          ------
    0x0058:         fc

    Write the new value to the card’s EEPROM

    The new hex value below is identical to simply flipping the last binary bit in the fc hex value.

    $ sudo ethtool -E eno1 magic 0x10fb8086 0x58 value 0xfd

    Let’s check if we were successful

    If the output of the command below returns the new value of our 0x58 offset, we should be good to go!

    $ sudo ethtool -e eno1 offset 0x58 length 1
    Offset          Values
    ------          ------
    0x0058:         fd

    What’s best about this EEPROM change is that is survives reboots, poweroffs, and will enable usage of unsupported SFPs in non-Unix operating systems.

    And, to return the EEPROM to its original state, simply write the old hex value back to the 0x58 offset and call it a day.

    1https://forums.servethehome.com/index.php?threads/patching-intel-x520-eeprom-to-unlock-all-sfp-transceivers.24634/
  • gpg-agent is older than us

    I use pass for password management and I ran into the following error when trying to retrieve a passphrase from its GPG store this morning:

    gpg: WARNING: server 'gpg-agent' is older than us (2.2.13 < 2.2.15)

    To fix this mismatch in version (after an update to GnuPG was installed) you need to kill the running version:

    $ gpgconf --kill -v gpg-agent

    After that pass should work once more, and not throw the “older than us” error.

    1https://medium.com/@doronsegal/gpg-agent-is-older-than-x-e8860a383cb0
  • Automating a Chinese robot vacuum

    A Roomba–or equivalent robot vacuum–is something I never felt I needed to purchase for myself. I have an old broom from colleg and a decent, budget upright vacuum. But, what happens when you’re gifted with a robot vacuum…?

    Behold, the holiday-gifted Ecovacs Deebot N79:

    A good boy

    Once rated Wirecutter’s Best Budget Robot Vacuum, the Deebot N79 fills me with both pride and guilt as it conquers the 2D world of my apartment. Whether it’s effortlessly chewing up cat litter, or getting irreparably tangled in lamp cords.

    Deebot has been a great addition to our home, but brings with it a few unavoidable facts:

    -The iOS app is garbage and provides no HomeKit support
    -The included handheld remote works, but speaks IR to Deebot (and can’t control him around corners)

    Trying to set a schedule on the handheld remote itself only works with near line-of-sight. And the schedule settings in the iOS app provide almost zero granularity. It provides no fine-tuned control of how long Deebot runs, whether it does full-room or edge cleaning, etc.

    Make Deebot do the WiFi

    In order to control Deebot over the network, we need to connect Deebot to the network. The instructions here speak for themselves.

    Once you’ve created your Ecovacs account on a server instance somewhere in China, try using the app to do the Deebot basics–cleaning, returning to the charge station, and directional control.

    Now Deebot is yet another WiFi device in your home.

    Install sucks

    William Pietri on GitHub created a set of python scripts that allow you to control your Ecovacs robot vacuum from the command-line.

    Install them with pip:

    $ pip install sucks

    Let’s log in to the Ecovacs servers with the credentials you just created:

    $ sucks login
    Ecovacs app email: <your email>
    Ecovacs app password: <your password>
    your two-letter country code: us
    your two-letter continent code: na
    Config saved.

    With that all set-up, let’s try to make Deebot do stuff from the CLI:

    $ sucks clean 5
    $ sucks clean 20 edge 15
    $ sucks stop
    $ sucks charge

    Cron your vacuum

    Lastly, let’s make a cron entry to run Deebot every day at 4PM local time:

    $ crontab -e
    0 16 * * * /<wherever_you_put_the_executable>/sucks clean 15 edge 10

    The above entry will–everyday at 16:00–command Deebot to perform a normal clean operation for 15 minutes, followed by an edge operation for 10 minutes, then instruct Deebot to return to its charging home.

    What next?

    Pietri’s project also includes a python library, so you can further add programatic logic to your vacuum.

    Maybe you could:

    -Have Deebot wake you up in the morning
    -Have Deebot keep your cats company
    -Have Deebot run a cleaning operation whenever Mom calls

  • Enable AirPods pairing on Arch Linux

    It’s beyond easy to pair AirPods with a non-Apple device. And on Arch, it’s trivial to have the AirPods appear in the bluetooth manager (in this case, blueman-applet)–but when you try to initiate pairing, it does not succeed.

    The trick here–if you don’t require pairing with Bluetooth LE devices–is to manually specify that the bluetooth controller (bluez) use BR/EDR transport rather than allow both transport modes.

    Open a shell and do the following:

    $ sudo vi /etc/bluetooth/main.conf

    Uncomment and manually set ControllerMode to BR/EDR:

    # Restricts all controllers to the specified transport. Default value
    # is "dual", i.e. both BR/EDR and LE enabled (when supported by the HW).
    # Possible values: "dual", "bredr", "le"
    ControllerMode = bredr
    

    Restart the bluetooth service:

    $ sudo systemctl restart bluetooth

    Try re-pairing!

  • Mac-like copy/paste on Fedora

    2018 is the year of Linux on the desktop—but only for me and only since I started working at a new company where I use Fedora for ~ eight hours daily. Coming from 10+ years of using Macs at work, the biggest change is using Ctrl + C/V for copy/paste and not Cmd + C/V. A couple of weeks in and my hand began to get a slight cramp from using my pinky and index fingers rather than my thumb and index fingers for the operation.

    After reading how to remap keys with xmodmap, the configuration below is what I came up with.

    Once parsed by Xorg, this lines in this file will remap right alt to right super, left alt to right control and left super to left alt.

    $ vi ~/.Xmodmap
    clear control
    clear mod1
    clear mod4
    keycode 133 = Alt_L Meta_L
    keycode 37 = Control_L
    keycode 64 = Control_R
    keycode 108 = Super_R
    add control = Control_L Control_R
    add mod1 = Alt_L Meta_L
    add mod4 = Super_R

    Confirm that this file is in your home directory, logout, log back in, and test. This may break existing shortcuts, so be sure to remap as neccessary under Settings > Keyboard > Application Shortcuts.