Intro

Build Status matrix

Attempting to organize my notes with mdBook.

Rust

Installing Rust

I really dislike curlpipes, and installing software outside of package managers. And cargo is a really good package manager, but unfortunately theres a lack of good Rust packages in Linux distro repos. The official Rust Forge documentation (supplementary docs for Rust, a lot of interesting stuff there around how they manage the project, infrastrucutre, build channels, design desiciions and such) doesnt really mention any good packages in Linux distros or particularly recommend using the ones that do exist.

So im using the curlpipe rustup.sh script..

After having just updated with rustup update:

amine:~$ cargo --version
cargo 1.71.1 (7f1d04c00 2023-07-29)

There is also a rustlang PPA on Launchpad with stable and nightly builds that ive seen mentioned.

I should probably build and package Rust and friends for my builds repo some day..

Cross compiling

Easiest way that i have found is to use cross. It cross-compilers in docker (or podman) containers, that it manages itself.

You can also use cargo directly with cargo build --target=$target, but i havent given that more than a cursory attempt (but i should, to understand how cross relates to cargo better)

  • make sure to have your user in the docker group to avoid root.

You can get a list of supoprted targets (platforms?) from rustc:

$ rustc --print target-list

For more details on the different targets and how well they are supported, check the rustc documentation about about Platform suppport and the for cross specifically, the cross/README.md also has a list.

Configuring cargo

If you are cross compiling, then you are most likely targeting a platform that you cant or dont want to compile on. Basically something less powerful and common than say x86_64 (or at least I am). So you'll most likely want a statically linked binary. You dont configure that in Cargo.toml, because that is for configuration of your project itself, and we need to configure cargo (not the project that it is building).

  • targets with musl are generally preferred for building statically linked binaries.

So you need to add that to cargo.toml, which can live in a couple of places, the ones that i use are

  • $HOME/.cargo/config.toml
  • $PWD/.cargo/config.toml

Note that youll sometimes see it referred to without the .toml suffix as .cargo/config -- thats a legacy name for the same file.

Making static builds

To get a static build, you need to pass -C target-feature=+crt-static to rustc. So if you're running rustc directly, this should work:

rustc -C target-feature=+crt-static

But you probably arent, and you probably want to use the cargo config. If you use the cargo config, you can specify options for multiple targets in the same place, which is kind of neat:

[target.x86_64-unknown-linux-musl]
rustflags = ["-C", "target-feature=+crt-static"]

# my vacuum cleaner
[target.aarch64-unknown-linux-musl]
rustflags = ["-C", "target-feature=+crt-static"]

# my router
[target.armv7-unknown-linux-musleabihf]
rustflags = ["-C", "target-feature=+crt-static"]

# my access point
[target.mips-unknown-linux-musl]
rustflags = ["-C", "target-feature=+crt-static"]

Then to compile, say for my vacuum cleaner:

amine ~/projects/robot $ cross build --target aarch64-unknown-linux-musl --release
   Compiling hello_robot v0.1.0 (/project)
    Finished release [optimized] target(s) in 0.16s

You'll find the compiled binary in target/${target}/release (assuming you built with --release):

amine ~/projects/robot $ file target/aarch64-unknown-linux-musl/release/hello_robot
target/aarch64-unknown-linux-musl/release/hello_robot: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

Transfer it somehow to the machine with the architectory you targetted, and it will .. just work:

amine ~/projects/robot $ scp target/aarch64-unknown-linux-musl/release/hello_robot vacuum-robot:/data
hello_robot

[root@vacuum-robot ~]# /data/hello_robot
Hello, world!

As a sidenote, if you have a robot vacuum and you havent rooted it, then you are missing out.

Useful commands

If youre somewhere without rust installed, can can use the docker images. To get the list of supported targets for example:

$ docker run --rm --user "$(id -u)":"$(id -g)" rust:latest rustc --print target-list

Python

Notes

Use poetry-bumpversion with poetry. Its a poetry plugin, and not tied to the project (wich would have been nice).

poetry self add poetry-bumpversion

And in your pyproject.toml, configure as needed:

[tool.poetry_bumpversion.file."${module_name}/__init__.py"]
[tool.poetry_bumpversion.file."tests/test_version.py"]

With this example it will update __version__ in ${module_name}/__init__.py to the version in pyproject.toml, and also keep the version number in tests/test_version.py up to date.

Ansible

Home Automation

Detecting rain

NOTE: these are notes on on ongoing "project" that i sporadically work on, not actual documentation of how to achieve anything

Hydreon RG-9

  • https://rainsensors.com/products/rg-9/
  • https://rainsensors.com/support/rg-mounting-alternatives/
  • https://rainsensors.com/support/rg-9-rg-15-faq/
  • https://community.home-assistant.io/t/hydreon-rg-15-rain-sensor/332794
  • https://www.esphome.io/components/sensor/hydreon_rgxx

Z-Wave tipping bucket: POPE700168

Weather APIs

Hacking my door phone connect it with Z-Wave

Background

The intercom/buzzer/doorphone in my flat is a Siedle HTS 711-01, and i wanted to be able to press the the button remotely, to trigger the lock downstairs in my building.

The ringer also makes a deeply horrible sound when some rings my bell, the next part for this project is to sense the ring singlas when this happens, and play an actually pleasant sound instead. Being able to automate and schedule when it is on/off based on the rest of the state of my flat will also be nice. But i started with the easy part, triggering the door unlocking.

Adding a relay to trigger the buzzer

Since the buzzer for the house door is triggred by a simple switch, all we need is a relay that we can control remotely. I personally mainly use Z-Wave (with Z-Wave JS) devices, controlled with Home Assistant.

I used used a Fibaro FGBS-222 implant that i had on hand, which is (among other things) a relay.

wires soldered to the contacts for the buzzer button

This is a really simple switch, it just closes a circuit which triggers the buzzer. I soldered wires to each end of the open side of the circuit that the button closess, and use one of the relay contacts on the FGBS-222 to close it.

Home Assistant templating

Configuration for Home Assistant can be found at infra:roles/hass/files/packages/doorbell.yaml:

---

script:
  momentary_switch:
    icon: mdi:button-pointer
    mode: parallel
    sequence:
      - service: switch.toggle
        target:
          entity_id: "{{ target_switch }}"
      - delay:
          milliseconds: "{{ press_for_ms | int }}"
      - service: switch.toggle
        target:
          entity_id: "{{ target_switch }}"
    fields:
      target_switch:
        description: >-
          entity_id of the switch to toggle like a button (a list of
          entity_id's also works)
        example: switch.smart_implant_out1
      press_for_ms:
        description: how long to press the button, in milliseconds
        default: 200

template:
  - binary_sensor:
      - name: doorbell_buzzer
        state: >-
          {{ is_state("switch.doorbell_buzzer", "on") }}
        icon: >-
          {% if is_state("switch.doorbell_buzzer", "on") %}
          mdi:electric-switch-closed
          {% else %}
          mdi:electric-switch
          {% endif %}

  - button:
      name: doorbell_buzzer
      icon: >-
        {% if is_state("switch.doorbell_buzzer", "on") %}
        mdi:electric-switch-closed
        {% else %}
        mdi:electric-switch
        {% endif %}
      press:
        - service: script.momentary_switch
          data:
            target_switch: switch.doorbell_buzzer
            press_for_ms: 200

This assumes that the switch entity on the FGBS-222 module is named switch.doorbell_buzzer, and creates a button.doorbell_buzzer entity that triggers the relay.

Files

Filename
Systemhandbuch_1+n-Technik_2019_210009634-00_DE.pdfDatasheet and wiring diagrams

CHANGELOG

DateComment
2023-03-08Connected the buzzer to Fibaro FGBS-222 and HA
2023-09-08Added example YAML config for HA

Notes on using Apple TV (mostly with Home Assistant)

NOTE: these are notes on on ongoing "project" that i sporadically work on, not actual documentation of how to achieve anything

For a while my hass instance would loose track of the Apple TV if it went into a sleeping state. This was a bit of a problem for me, as I mainly relied on the state of my ATV to turn my (old and power hungry amp) on and off.

This was a known bug and had been fixed but the problems persisted for me, and it turned out that i had a config issue (probably as well as being affected by the bug). Saw the commit 33387bf and got clued in to what was going on, the hass integration is relying on the "Companion Protocol".

I had added it by hostname/ip, and it turns out that it relies heavily on the mdns advertisements from the ATV and its "identifiers" instead. Adding it as the "name" of the ATV (mine is creatively named "Apple TV") and now hass doesnt loose track of it when it goes to sleep and is immediately aware of the state of the ATV as soon as it wakes up.

This reliance on mdns and discovery requires you to run hass on the same network, which is an annoying constraint (though I do run it on the same network).

Can we use a proxy?

The hass integration uses pyatv, which comes with the (rather neat) atvproxy tool, which can proxy the companion protocol (and all the others too). Idea: can we use that as a bridge between home assistant and ATV?

First, we have to find the Apple TV and the identifiers:

amine ~ $ atvremote scan
Scan Results
========================================

       Name: Apple TV
   Model/SW: Apple TV 4K, tvOS 16.6
    Address: 10.1.1.14
        MAC: 8C:42:99:82:4C:CB
 Deep Sleep: False
Identifiers:
 - 8B2E0556-6BFF-46AB-B977-3D8E678D38A4
 - 8C:42:99:82:4C:CB
 - 8C4299824CCB

Then we pair atvremote with the ATV for the companion protocol (note how we direct it to the ATV with the UUID, and not ip/hostname):

amine ~ $ atvremote --id 8B2E0556-6BFF-46AB-B977-3D8E678D38A4 --protocol companion pair
Enter PIN on screen:

After that it spits out an authentiction token, which i saved as ~/.config/atv.credentials. Then we can start atvproxy for the companion protocol:

amine ~ $ atvproxy companion `cat ~/.config/atv.credentials` 10.1.1.14
2023-08-18 00:50:30 DEBUG [pyatv.scripts]: Running with pyatv 0.13.4
2023-08-18 00:50:30 DEBUG [pyatv.scripts.atvproxy]: Binding to local address 10.1.1.13
2023-08-18 00:50:30 INFO [pyatv.scripts.atvproxy]: Started Companion server at port 46507
2023-08-18 00:50:30 DEBUG [pyatv.core.mdns]: Publishing zeroconf service: ServiceInfo(type='_companion-link._tcp.local.', name='Proxy._companion-link._tcp.local.', [....])

And now the proxy appears as an Apple TV named Proxy on the network, which the hass integratoin can connect to.

CHANGELOG

DateComment
2023-08-18initial notes on pairing and starting atvproxy

wwwsudois

mdBook on /docs

Relevant docs ive been looking at:

building

install mdbook:

cargo install mdbook

and build it:

mdbook clean
mdbook build

to get a dev server:

mdbook serve

sudo.is infrastructure

/deadspace

nullspace

Command reference

A place to note and reference how to run various commands

apt-key is deprecated, how to add repos and keys

This (now) familiar error:

W: GPG error: http://download.example.com/debian bookworm InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 1140AF8F639E0C39
E: The repository 'http://download.example.com/debian bookworm InRelease' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

The path where keys are stored has changed, and you sneed a signed-by attribute in the repo .list file referencing the key's path.

TLDR

  • new path for key files, use /etc/apt/keyrings (or /usr/share/keyrings if its your repo)
  • add a signed-by to the .list file, refercing the filename:
    deb [arch=amd64 signed-by=/etc/apt/keyrings/key.gpg] http://download.example.com/debian bookworm main
    
  • remove old keys from apt-key

De-armoring the key

Convert the file from the ASCII armor to binary format (the pipe is important, havent found the right args to gpg to do it without stdin).

$  file key.asc
key.asc: OpenPGP Public Key Version 4, Created Mon Nov  9 06:59:32 2020, RSA (Encrypt or Sign, 4096 bits); User ID; Signature; OpenPGP Certificate

$ cat key.asc| gpg --dearmor > key.gpg

$ file key.gpg
key.gpg: OpenPGP Public Key Version 4, Created Mon Nov  9 06:59:32 2020, RSA (Encrypt or Sign, 4096 bits); User ID; Signature; OpenPGP Certificate

This step isnt really needed, debian just recomennded it for compatability reasons

Add the repo referencing the file

Move it into place and ensure correct ownership:

$ sudo cp key.gpg /etc/apt/keyrings/
$ sudo chown root:root /etc/apt/keyrings/key.gpg
$ sudo chown 644 /etc/apt/key.gpg

Add the repos .list file with ansible:

- name: add apt repo
  apt_repository:
    repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/key.gpg] http://download.example.com/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} main"
    #repo: "deb [arch=amd64] http://download.proxmox.com/{{ ansible_lsb.id | lower }}/pve {{ ansible_lsb.codename | lower}} pve-no-subscription"
    state: present
    update_cache: false
    filename: /etc/apt/sources.list.d/example

Which should look something like this

$ cat /etc/apt/sources.list.d/example
deb [arch=amd64 signed-by=/etc/apt/keyrings/key.gpg] http://download.example.com/debian bookworm main

With signed-by referencing where the file exists on your filesystem.

Alternative repo defintions and ansible modules

Alternatively use the deb822_repository module:

- name: add your repo
  deb822_repository:
    name: example
    types: deb
    uris: http://download.example.com/{{ ansible_distribution | lower }}
    suites: "{{ ansible_distribution_release }}"
    components: stable
    architectures: amd64
    signed_by: /etc/apt/keyrings/key.gpg

Or even more alternatively instead of .list file, create a .sources file like /etc/apt/sources.list.d/example.sources:

Types: deb
URIs: http://download.example.com
Suites: {{ ansible_distribution | lower }}
Components: main
Signed-By: /etc/apt/keyrings/key.gpg

Clean up old keys from apt-key

Then clean up the key from apt-key if it was there already. List existing keys with

$ sudo apt-get list

Debian/ubuntu keys are still there for compatability reasons, so grep them out:

$ sudo apt-get list | grep uid | grep -vi debian

If that turns up any keys, delete them:

$ sudo apt-key del support@example.com.

Now you can apt update and apt install and etc.

basic certbot usage

Create (request) a new cert for ${name}:

certbot certonly -d ${name}

The new cert exists in the certbot-managed dir /etc/letsencrypt/live:

~:$ ls -d /etc/letsencrypt/live/${name}
/etc/letsencrypt/live/${name}

If you need to delete/revoke a cert for ${name}:

certbot delete --cert-name ${name}

Stream usb camera with VLC without transcoding

cvlc v4l2:///dev/video0 --sout '#standard{access=http,mux=ts,dst=:8080}'

Git submodules

By default they are added at fixed commits. Now git can set submodules to track branches. Its still fiddly and im not sure its working correctly for me.

Adding a new submodule tracking $branch:

branch=main

# add submodule to track a branch
git submodule add -b $branch $url;

# update submodule
git submodule update --remote

Change an existing submodule to track $branch:

submodule=foo
branch=main

# change the submodule defintion in the parent repo
git config -f .gitmodules submodule.${submodule}.branch $branch

# and make sure the submodule itself is actually at that branch
cd $submodule
git checkout $branch
git branch -u origin/$branch $branch

This how I have currently used it in ben/builds, but not sure its correct. Also not sure that repo is a good idea or if I should go back to separate repos.

References

Firefox

Use Mozilla's PPA on Ubuntu

Nobody likes snap, and Mozilla maintains the latest stable Firefox in a PPA:

ppa:mozillateam/ppa

Start by removing the snap version that Ubuntu installs:

sudo snap remove firefox
sudo systemctl stop var-snap-firefox-common-host\\x2dhunspell.mount
sudo systemctl disable var-snap-firefox-common-host\\x2dhunspell.mount
sudo apt-get purge firefox

Then add the PPA: and install the mozilla firefox package:

sudo add-apt-repository ppa:mozillateam/ppa

Ubuntu will still prefer its package that installs firefox with snap, so create /etc/apt/preferences.d/mozilla-firefox:

Package: firefox*
Pin: release o=LP-PPA-mozillateam
Pin-Priority: 501

Package: firefox*
Pin: release o=Ubuntu
Pin-Priority: -1

And then apt should prefer the package from Mozilla after you update the cache:

amine ~ $ sudo apt update

amine ~ $ apt-cache policy firefox
firefox:
  Installed: (none)
  Candidate: 116.0.3+build2-0ubuntu0.23.04.1~mt1
  Version table:
     1:1snap1-0ubuntu3 -1
        500 http://de.archive.ubuntu.com/ubuntu lunar/main amd64 Packages
     116.0.3+build2-0ubuntu0.23.04.1~mt1 501
        500 https://ppa.launchpadcontent.net/mozillateam/ppa/ubuntu lunar/main amd64 Packages

Now just install the firefox package:

sudo apt install firefox

Sources (with more info):

Process manager

Firefox has a process manager now, open:

about:processes

Add-ons

Firefox add-ons that i find useful:

  • Dark reader
  • uBlock origin
  • Certificate pinner
  • Auto tab discard
  • Privacy redirect

KDE

documentation of various things i do with KDE or Plasma

clean and read-only desktop

create empty dir and make it unwritable

~:$ mkdir ~/.empty
~:$ chattr -fR +i ~/.empty
~:$ lsattr -d ~/.empty
----i---------e------- ~/.empty

optionally set desktop to use that (should use XDG values). for Plasma, this is in ~/.config/plasma-org.kde.plasma.desktop-appletsrc:

url=/home/${USER}/.empty/

About

Keys

SSH

SSH key: ben.pub

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID7N1IzGvC1y9XEBcI+cuZdEoEQxkoXLZkD1KCGox3l1 ben@sudo.is

SSH key (RSA): ben-rsa.pub

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/iaS4ouXCPD80OfYHFIHIM+w9ZM5yTEeLEADAMGSyYMS/EABx5KFAk9Go6va51x2UL3dl/q110MWHPe7lYelhUMD+lqCFGCw7ZS87xZvXdrzp9QVNWC4cUaUGqxu4WhLAqmgTrcSG0V8luRGpyKx9nTwm8/n3um8xMx3CVuEW1t2hVhwPfRrXUzZ7RisQ1cyfHReqSDCW40U1JeMyKy0SqQOzKRB91cLaOjJUeCk/BZGgGukU83R5Wz4cG7U+056U1y+5Rb2qcgKAVn44mmdfDuyUF6y2XnXp7Z6KMR4R/SJvj94FAuAN1e9tjMc41yTtgYI+9We2uuLGtEhZTSvvXkEx5kOfuA8OVJKgVV0VO6YcJMQD47k2BXNcx+YzZ3XaEtqKY0DZ3vI3+5tFXohmVrvJxEna5QWF7B7yN+dH1HethPd7ZAIARezmRi6mkGnE5UURgJLqgMkKdkZJoN1ZgQvSb+ZoOhUdEHbxoJI8+LRajJuahSalIjuZHdjK/02TUPyHnhzir47LC47gcp0NXxcrD87ASXserR9IvG5Vr+mlW/YXv8wpps2jzR+0cyfxJwPsCn+wTHWvSO0I3obyRLtkBNjmlz+wTv6jBn8XVQ2A1QSnzfAiCSp85gBAXfe14XfJuZgOHBuUcGFEcNSztM5SYxVdUXib9+q4ndqOEQ== ben@sudo.is

GPG

GPG key: ben.asc

GPG key (including signatures): ben-signs.asc

-----BEGIN PGP PUBLIC KEY BLOCK-----

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
eL3512pyyn4WW8tnD62Ks5Q=
=7L1z
-----END PGP PUBLIC KEY BLOCK-----