Intro

These are a collection of useful personal notes about various technical work that I have experienced or done and may be useful for others.

Over time information in these notes will become outdated. The point is not to keep an updated set of technical notes but to have snapshots of how things could actually be done (though if I do learn updated methods, I will update the notes)

Plenty of other interesting information is available in my dotfiles

Hardware

Various notes about configuring/setting up/using various hardware

IR Transceiver

This section is mainly about capturing IR signals from a Bryant IR-based remote (for a ductless system) to be able to replicate and playback the messages via software (allowing for home/lan integrations). This method would work for other ductless systems from other manufacturers though some changes may be required. Other USB transceivers may also work but that would likely require various changes to get lirc working properly.

Context

The reason all of this is even necessary is that lirc by itself has a number of tools all around dealing with IR reception/transmission BUT irw, irrecord, and irsend only understand the "remotes" (or codes) that the underlying lirc configs understand. Most of these are for things like TV remotes (e.g. power on, volume up) and not for anything quite like a ductless AC remote. In order to talk to such a different device one has to first get the raw data from the remote in question and then "teach" lirc how to speak those codes (via configuration).

Hardware

  • Alpine Linux (3.13), lirc 0.10.1-r0
  • x86-64 server (also testing on a pi4 with Alpine 3.13)
  • Irdroid USB IR Transceiver
  • Bryant ductless system (Models: 619PAQXXXBBMA, 619PEQXXXBBMA)

Also works in Alpine 3.14

Setup

  • USB transceiver plugged in
  • lirc installed
  • lsusb reports the Irdroid device

Capturing

We need to capture the raw device inputs, to do that:

mode2 -d /dev/ttyACM0 -H irtoy > output

Press a single button on the remote and then CTRL+C

(note: conceptually ir-ctl can be used to playback the output file directly)

There is an extraneous spacing output that will probably be the final output line, this is not part of the code we need so remove it

sed -i '$ d' output

Now make sure we only capture the code outputs (and none of the other extra mode2 outputs)

cat output | grep '^(spac|pulse)' | cut -d " " -f 2 | tr '\n' ' ' > code

This will have captured a single button press. It's important to understand that these remotes maintain the state of the system and therefore a "power on" press at 72 degrees will show up differently than a "power on" at 74 degrees (basically many commands must be captured if a lot of settings are wanted)

Config

First define the remote type and that we're using raw codes to communicate

vim bryant.conf
---
# ACSTOP (74 degrees)
# ACSTART (74 degrees)
begin remote

    name BRYANT
    flags RAW_CODES
    eps 30
    aeps 100

    ptrail 0
    repeat 0 0
    gap 40991

    begin raw_codes

Next specify the name of the command to include

vim bryant.conf
---
        name ACSTOP

Finally the raw output

cat code >> bryant.conf

For any additional codes one must just create a name <NAME> and then the output (see the full example below)

Close-out the remote configuration

    end raw_codes
end remote

lirc

Now make sure to run lircd with the configuration

ircd -H irtoy -d /dev/ttyACM0 bryant.conf

and then send commands!

irsend SEND_ONCE BRYANT ACSTART

Example

# ACSTOP (74 degrees)
# ACSTART (74 degrees)
begin remote

    name BRYANT
    flags RAW_CODES
    eps 30
    aeps 100

    ptrail 0
    repeat 0 0
    gap 40991

    begin raw_codes
        name ACSTOP
            4415 4394 554 1578 554 511 554 1578 554 511 554 511 554 511 554 511 554 1578 554 511 554 511 554 1578 554 511 554 511 554 511 554 511 554 511 554 511 554 1578 554 1578 554 511 554 1578 554 1578 554 511 554 511 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 511 554 511 554 511 554 511 554 1578 554 511 554 5183 4415 4394 554 511 554 1578 554 511 554 1578 554 1578 554 1578 554 1578 554 511 554 1578 554 1578 554 511 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 511 554 511 554 1578 554 511 554 511 554 1578 554 1578 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 1578 554 1578 554 1578 554 1578 554 511 554 1578 554
        name ACSTART
            4415 4394 554 1578 554 511 554 1578 554 511 554 511 554 511 554 511 554 1578 554 1578 554 511 554 1578 554 511 554 511 554 511 554 511 554 511 554 511 554 1578 554 1578 554 511 554 1578 554 1578 554 511 554 511 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 511 554 1578 554 511 554 511 554 511 554 511 554 1578 554 511 554 5183 4415 4394 554 511 554 1578 554 511 554 1578 554 1578 554 1578 554 1578 554 511 554 511 554 1578 554 511 554 1578 554 1578 554 1578 554 1578 554 1578 554 1578 554 511 554 511 554 1578 554 511 554 511 554 1578 554 1578 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 511 554 1578 554 511 554 1578 554 1578 554 1578 554 1578 554 511 554 1578 554
    end raw_codes
end remote

Kinesis Advantage 2

Using a kinesis advantage 2 (on Linux) is not any different than any other operating system. A few useful notes, though, about working with it.

Export

To "export" the current configuration:

  1. Go into Power User Mode (progm+shift+esc)
  2. Enable vdrive (progm+F1)
  3. mount -t vfat /dev/sda /mnt/vdrive (or whichever device gets mapped, notice the lack of the partition)
  4. Poke around in the mounted area, mainly in the active/ directory
  5. Make sure to umount /mnt/vdrive
  6. Close Power User Mode (same key press as 1)

QMK

There is a lot of documentation (by QMK itself) about how to use their tooling to flash systems. The problem one runs into is trying to do this from Linux directly (therefore no QMK Toolbox).

QMK is an extensive project but much of the documentation/tooling is so generic that finding out how to "do things by hand" requires a bit of digging (looking through QMK Toolbox to extract commands to manually flash a keyboard). It's possible to fully compile the firmware and flash a keyboard on Linux from the command line.

keebio/iris/rev6

For example in order to flash a keebio iris (rev6) from (Alpine) Linux:

  • Install: dfu-programmer, qmk-cli, gcc-avr, and avr-libc
  • Expect qmk setup to complain about all the missing tools for other keyboards that don't matter for this ONE keyboard
  • Run qmk json2c the.json > keyboards/keebio/iris/keymaps/enckse/keymap.c if the online configurator was used and the json was downloaded
  • Run qmk compile -kb keebio/iris/rev6 -km enckse

Now, at this point, it's probably easier to run dfu-programmer against each half of the keyboard (remember to set it for flashing):

sudo dfu-programmer ATmega32U4 erase --force
sudo dfu-programmer ATmega32U4 flash --force enckse.hex
sudo dfu-programmer ATmega32U4 reset

Of course different keyboards will require different, specific tools but QMK itself is definitely trying to cover a LOT more than a single keyboard needs day-to-day

Remarkable2

The remarkable2 table is an eink notebook.

Using

It's entirely possible to use the remarkable2 without using connect/cloud services

Connecting

Connect via USB and configure the usb device with the following

auto usb0
iface usb0 inet static
    address 10.11.99.3
    netmask 255.255.255.249

Import/Export

To export notebooks or import PDFs:

  • Connect
  • Make sure, under storage settings on the remarkable, to enable the web interface
  • Navigate to http://10.11.99.1 and notebooks can be exported
  • Drag-and-drop PDFs onto the web interface to add PDFs to the remarkable

Templates

To make a new notebook template

  • Connect
  • Prepare to ssh [email protected] using the password configured on the remarkable
  • Make sure the image is 1404x1872 and a PNG.
  • Place the image in /usr/share/remarkable/templates/
  • Edit /usr/share/remarkable/templates/templates.json and define the template (may want to back this up first)
  • Restart the UI: systemctl restart xochitl

Linux

General notes about various distributions, tools, etc.

Certificates (acme.sh)

Remember, acme.sh is not an EFF/Let's Encrypt project so using it may come with some side-effects

acme.sh can be used as a replacement to something like certbot. One should install acme.sh via the distribution package management system.

data

By default acme.sh will store/manage data in the user's home folder under .acme.sh (e.g. /home/myuser/.acme.sh) but this data should only ever be touched by acme.sh and not used/touched by the user.

account

To associate an email with the account for acme.sh operations

acme.sh --update-account --email <your email address>

issue/renew

To issue (--issue) or renew (--renew) utilizing multiple domains and via a webroot.

acme.sh --issue -d my.example.com -d my.other.example.com -w /path/to/webroot

deploy

One can then actually get the useful certificate files for something like nginx via:

acme.sh --install-cert -d my.example.com -d my.other.example.com \
    --key-file /some/path/to/my.example.com/or/other/key.pem \
    --cert-file /some/path/to/my.example.com/or/other/cert.pem \
    --fullchain-file /some/path/to/my.example.com/or/other/fullchain.pem

Which map to nginx directives:

acme.sh CLInginx config
--key-filessl_certificate_key
--cert-filessl_trusted_certificate
--fullchain-filessl_certificate

Alpine Boot

EFI

Using EFI in Alpine is supported but the documentation is not consistently available in the wiki where one would expect.

environment

Setup the necessary environment variables

export BOOTLOADER=grub
export USE_EFI=1

packages

Drop syslinux and add grub/friends

apk del syslinux
apk add grub grub-efi efibootmgr

Arguments

Information that expands upon the documentation for Alpine's initramfs command line options.

ssh_key

  • installs openssh
  • enables/starts sshd
  • takes the string literal from ssh_key and puts that value into /root/.ssh/authorized_keys

apkovl

APK overlay file to download and apply onto the system (this is a URL). Placing the file using the device:fstype:path method does not appear to work.

These are generally considered "Alpine local backups" and managed via lbu if seeking more information.

alpine_repo

URL to use as the repository in the system

ip

In order to get an IP one can use =dhcp OR define everything important to get a network connection during init:

172.16.0.200:none:172.16.0.1:255.255.255.0:myhostname::none:1.1.1.1
^ ip address to request
             ^ server ip
                  ^ gateway
                             ^ netmask
                                           ^ system hostname
                                                      ^ autoconf is N/A
                                                            ^ DNS

Alpine Package Cache

Utilizing nginx to provide an Alpine Linux package cache for apk files. The idea is, on a network, to use nginx to provide a common repository of cached packages that any machine has requested that can then be re-used by subsequent requests.

Below is an example configuration file (that would then be "included" into an nginx.conf)

Such a configuration could also be possible, with the necessary updates, for other distributions (e.g. ArchLinux)

/etc/nginx/apk.conf
---
server_names_hash_bucket_size 128;

server
{
    listen      9999;
    root        /srv/http/apk/;
    autoindex   on;

    # Requests for package db, signature files and files db should redirect upstream without caching
    location ~ \.tar\.gz$ {
        proxy_pass http://mirrors$request_uri;
    }

    # Requests for actual packages should be served directly from cache if available.
    #   If not available, retrieve and save the package from an upstream mirror.
    location ~ \.apk$ {
        try_files $uri @pkg_mirror;
    }

    # Retrieve package from upstream mirrors and cache for future requests
    location @pkg_mirror {
        proxy_store    on;
        proxy_redirect off;
        proxy_store_access  user:rw group:rw all:r;
        proxy_next_upstream error timeout http_404;
        proxy_pass          http://mirrors$request_uri;
    }
}

# Upstream mirrors
# - Configure as many backend mirrors as you want in the blocks below
# - Servers are used in a round-robin fashion by nginx
# - Add "backup" if you want to only use the mirror upon failure of the other mirrors
# - Use separate mirror server blocks to be able to use mirrors that have different paths to the package repos
upstream mirrors {
    server 127.0.0.1:8001;
}

# the proxy_pass directive should look like this
# proxy_pass http://mirror.domain.example/path/to/repo$request_uri;
#
# Notice that $request_uri replaces the /$repo/os/$arch part of
# the mirror address. See more examples below.

server
{
    listen      127.0.0.1:8001;

    location / {
        proxy_pass       http://dl-cdn.alpinelinux.org$request_uri;
    }
}

Which can then be referenced by an Alpine install

vim /etc/apk/repositories
---
http://<host>:9999/alpine/v3.14/main
http://<host>:9999/alpine/v3.14/community

Alpine Installation (Raspberry Pi 4)

Assuming the boot media is created (sd card) then the following should, generally, get alpine up and running

  1. Partition disks (as preferred) - leaving the boot partition alone
  2. Use ext4 BUT one will have to manually edit setup-disk to allow the fs for the Pi
  3. Make sure to mount the root partition to /mnt
  4. And then make sure to mount -o remount,rw /media/<boot device> and then mount --bind /media/<boot device> /mnt/boot
  5. Finally run setup-disk -m sys /mnt

This should get everything installed, after first boot one should edit fstab and make /boot/boot a bind mount to boot itself (FAT32 doesn't allow symlinking in this case)

Bind Mounts

A very useful tool in the Linux toolbox is a "bind mount" which can be used, for example, to mount a directory from one location to another (e.g. no symlinks, allows for more options like read only).

For example to bind mount a "new /var" over the old:

mount --bind /mnt/data/var /var

These can also be placed into /etc/fstab

Cloud Init

Notes about using cloud-init to bootstrap a small system (mostly virtualized in the following use cases)

networking

It can be easier to set a static IP via kernel parameters (e.g. ip= then it is to find the documentation for cloud-init networking which will tell you to use ip= or some other arcane method)

post-boot

a cloud-init system can end up no longer using ip= in some cases (e.g. Fedora cloud images) which means creating an /etc/sysconfig/network-scripts/ifcfg-Wired_connection_1 may be required to handle networking with contents like (for Fedora)

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
IPADDR=192.168.64.2
PREFIX=24
GATEWAY=192.168.64.1
DNS1=192.168.1.1
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
NAME="Wired connection 1"
DEVICE=enp0s1
ONBOOT=yes
AUTOCONNECT_PRIORITY=-999

vmlinuz/initramfs

These can be pulled out of most ISOs though the ISO needs to have been built with the virtio module if it is going to be used for virtualization (e.g. passing a kernel/initram to a process to start as a VM)

disks

It is easier to use a "cloud ready" image that is a disk image that can be booted directly (and not booting an installation media/iso). Then one can set the kernel parameter root=/dev/path2 as necessary to boot the rootfs object properly.

Disks

Reload partitions

When installing/partitioning it is sometimes necessary to reload partition information for a disk, this can be done via:

hdparm -z /dev/<disk>

DNF/RPMs

chroots

when building chroot environments in a dnf-based distribution (e.g. RHEL, Alma) to be able to utilize some dnf options to speed-up building/configuring chroots

For example to create a new chroot, using the host's release, use a "shared" cache, and keep a cache

dnf install -y --releasever=/ --installroot /path/to/chroot
--setopt=cachedir=/var/cache/dnf --setopt=keepcache=True package1 package2

unpack

to unpack the contents of an rpm

rpm2cpio <rpm> | cpio -idmv

LXC

nesting

To enable nesting (containers in an LXC container) one wants to include the /usr/share/lxc/config/nesting.conf in a machine's configuration. Though if, for example, something like an "apparmor" setting is in the config and apparmor is not installed it may require taking the values from the nesting config and placing them directly into the machine's config (mainly want to make sure things like /proc or /sys or /dev are setup properly as more than read-only)

This does have security implications

filesystems

lxc can do more than directory-based containers (including image files via loop, btrfs, lvm, etc.) during creation.

Memory

Dirty Memory

View status of dirty memory (e.g. sync is running before umount)

watch -n 1 grep -e Dirty: -e Writeback: /proc/meminfo 

Traffic Control (tc)

Any useful notes about using tc, one can use bmon to interactively review the impact of playing with tc

basic traffic shaping

An example, on Alpine Linux running as a router, of using ifb with tc to try and shape some traffic (in both directions)

#!/bin/bash
# the internal "lan" interface LAN devices are "attached" to
LAN="lan0"
# the ifb device to create/use
IFB="ifb0"

# make sure the ifb device exists
ip link add $IFB type ifb >/dev/null 2>&1
ip link set $IFB up

# using ifb, via lan, to handle interface redirection
tc qdisc add dev $LAN handle ffff: ingress
tc filter add dev $LAN parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0

# using testing numbers
tc qdisc add dev $LAN root handle 1: htb default 10
tc class add dev $LAN parent 1: classid 1:1 htb rate 900mbit
tc class add dev $LAN parent 1:1 classid 1:10 htb rate 850mbit
tc class add dev $LAN parent 1:1 classid 1:20 htb rate 500mbit
tc class add dev $LAN parent 1:1 classid 1:30 htb rate 250mbit

# ideally these would be different as upload != download
tc qdisc add dev $IFB root handle 1: htb default 10
tc class add dev $IFB parent 1: classid 1:1 htb rate 900mbit
tc class add dev $IFB parent 1:1 classid 1:10 htb rate 850mbit
tc class add dev $IFB parent 1:1 classid 1:20 htb rate 500mbit
tc class add dev $IFB parent 1:1 classid 1:30 htb rate 250mbit

# filtering by IP/subnets
tc filter add dev $LAN parent 1:0 protocol ip prio 1 u32 match ip src 192.168.1.10/32 flowid 1:20
tc filter add dev $IFB parent 1:0 protocol ip prio 1 u32 match ip src 10.0.0.0/24 flowid 1:30

Virtualization (virsh)

Notes about using virsh and related commands

Installation

Simple installation (using a vnc "display")

virt-install -n myvm \
    --memory 16384 \
    --cpu host \
    --cdrom /path/to/rhel8.4.iso \
    --disk size=50 \
    --graphics vnc,port=5901,listen=0.0.0.0,password=myvncpass \
    --network default \
    --vcpus 2 \
    --os-variant rhel8.4

or rebuild from a disk image with all the same settings, by changing:

virt-install -n myvm \
    ... \
    # size=50 becomes...
    --disk /path/to/existing/disk.qcow2 \
    ... \
    # remove --cdrom /path/to/rhel8.4.iso
    --import \
    ...

Which is helpful if the qemu XML file gets removed/deleted

once configuration/install is done then console=ttyS0,115200 can be added to the kernel parameters and one can attach to the machine console via virsh console myvm

attaching

disks

A block device can be directly attached

virsh attach-disk --domain myvm /dev/sda1 vdb --config --type disk

usb device

Need the code for the usb (e.g. lsusb -v) to place into the following file

vim usbdevice.xml
---
<hostdev mode='subsystem' type='usb' managed='yes'>
    <source>
        <vendor id='0x1234'/>
        <product id='0x5678'/>
    </source>
</hostdev>

Which can be attached to a machine

virsh attach-device myvm --file usbdevice.xml --config --persistent

The detach-device command can work to detach the usb device too

Tools

Various notes about tools/small projects.

Alpine in WSL

Configure Alpine in WSL (assumes WSL has been configured/setup)

Create WSL Image

Import Alpine into WSL (make sure the directory structure exists)

wsl --import AlpineX.YY C:\Path\To\Directory\For\Alpine\WSL .\alpine.version.tar.gz

Test getting into it

wsl -d AlpineX.YY

Configure the Image

May want to make sure any Terminals call as a user, but make sure to adduser that user first

wsl -d AlpineX.YY --user myuser

May want to install alpine-conf to run things like timezone setup (e.g. setup-timezone)

Troubleshooting

Mounts like C:\ are in /mnt/c but also \\wsl$ can access WSL from the Windows host

Git

one-liners

line counts

get line counts for all git-tracked files

for f in $(git ls-files); do printf "%-10s %s\n" $(wc -l $f); done | sort -n

Go

Some notes on working in go

Profiling

To do some CPU profiling, the following snippet could be used

import "runtime/pprof"

//...

f, err := os.Create("cpu.prof")
if err != nil {
    log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()

Then run

go tool pprof cpu.prof

To interrogate the resulting output file (e.g. top10).

Testing

alternatively call:

go test -cpuprofile cpu.out -memprofile mem.out .

on a single package (as that is all it allows)

gopls/goimports

gopls can be used like goimports from the command line

gopls format <goimports arguments>

GNU Privacy Guard (GPG)

Passwords

Change existing password

gpg --passwd <id/email>

Hyper-V

Static Guest IP

Enabling static IPs in guests

Host

In order to get a static guest IP, one can NOT use the default switch and instead must create "their own"

Make the switch

New-VMSwitch -SwitchName "MySwitchName" -SwitchType Internal

Get the necessary index

Get-NetAdapter

Set the IP settings

New-NetIPAddress -IPAddress 192.168.3.1 -PrefixLength 24 -InterfaceIndex {INDEX}

Setup NAT (internet access)

New-NetNat -Name MyNAT -InternalIPInterfaceAddressPrefix 192.168.3.0/24

Guest

Setup the guest IP (manually, no DHCP server exists)

vim /etc/network/interfaces
---
auto eth0
iface eth0 inet static
	address 192.168.3.2 
	netmask 255.255.255.0
	gateway 192.168.3.1

don't forget to also configure a DNS resolver

Image to TOTP Token

Given a relatively "clean" QR code image (without the underlying base32 code), one should be able to extract via decoding, e.g.:

package main

import (
	"bytes"
	"flag"
	"fmt"
	"image"
	_ "image/jpeg"
	_ "image/png"
	"os"

	"github.com/liyue201/goqr"
)

func main() {
	file := flag.String("image", "qrcode.png", "image to decode")
	flag.Parse()
	path := *file
	fmt.Printf("recognize file: %v\n", path)
	imgdata, err := os.ReadFile(path)
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}

	img, _, err := image.Decode(bytes.NewReader(imgdata))
	if err != nil {
		fmt.Printf("image.Decode error: %v\n", err)
		return
	}
	qrCodes, err := goqr.Recognize(img)
	if err != nil {
		fmt.Printf("Recognize failed: %v\n", err)
		return
	}
	for _, qrCode := range qrCodes {
		fmt.Printf("qrCode text: %s\n", qrCode.Payload)
	}
}

macOS

builtin

vnc

macOS can open vnc connections via open

open vnc://myvncserver:5900

disks

iso handling

alpine (rpi4) bootable disks

Partition the target

diskutil partitionDisk <target disk> MBR "FAT32" ALP 2GB "Free Space" SYS R

and then make sure to set it for booting

sudo fdisk -e <target disk>
> f 1
> w
> exit

Unpack the tar payload in the /Volumes/ALP directory, and

vim usercfg.txt
---
enable_uart=1
gpu_mem=32
disable_overscan=1

cloud ready

To create a cloud-init ready iso on macOS, place "user-data" and "meta-data" in a configs/ directory (or any name)

hdiutil makehybrid -o init.iso -joliet -iso -default-volume-name cidata configs/

(make sure to specify -joliet -iso because otherwise macOS will try to use -hfs which many systems will not have installed/ready)

mount/unmount

an iso can attached or detached via hdiutil

hdiutil attach <file.iso> -mountpoint /Volumes/mymount
# and then
hdiutil detach /Volumes/mymount

OpenSSL

imap

openssl can connect to an imap server and run commands (e.g. fastmail)

#!/usr/bin/env bash
{
  sleep 3
  echo "a1 LOGIN [email protected] myapppassword"
  sleep 3
  echo 'a1 LIST "" "*"'
  sleep 3
} | openssl s_client -connect imap.fastmail.com:993 -crlf >/dev/null 2>&1

PowerShell

Files

Empty

Generate empty files of size

fsutil file createnew filename.txt <filesize>

OpenSSH

Profile

noprofile

Connect without profile

ssh -t <host> bash --norc --noprofile

Host Keys

Scanning

scan keys for git forwarding/relaying/mirroring/etc.

ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts

Vim

Replace Mode (Startup)

If vim starts in replace mode:

.vimrc
---
nnoremap R <Esc> " Disable replace mode