Setting up software RAID on Debian with mdadm

Software RAID has come a long way. Unless you have some very high-rate, high-volume, I/O workloads with SLAs that will otherwise cost you money, for the most part a software RAID will perform just fine. One of the primary benefits of using software RAID is the portability of your disks/data. If a box on which you have a software RAID dies somehow and at least some (depending on your RAID configuration) of drives survive, you can easily resurrect the RAID on another machine without having a duplicate RAID card on hand.

Following is an example of how to setup a RAID 1 array using mdadm on Debian. I have two 2TB drives, /dev/sdb and /dev/sdc.

Install mdadm

apt-get install mdadm

Create the array

mdadm --create --verbose /dev/md0 --level=1 --raid-devices=2 /dev/sdb /dev/sdc

Once you create the RAID device, you can check on the mirroring process (Not sure what it is syncing with a blank disk…)

# cat /proc/mdstat
Personalities : [raid1]
md0 : active raid1 sdc[1] sdb[0]
1953382464 blocks super 1.2 [2/2] [UU]
[>....................] resync = 1.0% (20516736/1953382464) finish=190.1min speed=169401K/sec
bitmap: 15/15 pages [60KB], 65536KB chunk

Create a filesystem

mkfs.xfs /dev/md0

Create a mount point and mount the filesystem

mkdir /data
mount /dev/md0 /data

Ensure that the array is reassembled on boot by adding it to /etc/mdadm/mdadm.conf. We do this by scanning the active array and appending it’s details to the config file.

mdadm --detail --scan /dev/md0 >> /etc/mdadm/mdadm.conf

Upate the initramfs so that the array will be available on boot

update-initramfs -u

Add an entry to /etc/fstab

/dev/md0 /data               xfs     defaults,nofail,discard  0 0

Do a test reboot and make sure that it is mounted and the data accessible.

[SOLVED] debsig-verify for Failed verification error, “signatures using the SHA1 algorithm are rejected” and “Can’t check signature: Invalid digest algorithm”

If you are using debsig-verify for the verification of a downloaded .deb file and are unable to verify it, run it with the -d option to get more information. If you see the following two lines

gpg: Note: signatures using the SHA1 algorithm are rejected
gpg: Can't check signature: Invalid digest algorithm

It is likely that the PGP signature used to sign the package uses the SHA1 algorithm which has been deprecated in most of the recent Linux distros. If you can generate another PGP key with a different algorithm. If you are a consumer of this deb package and cannot get the maintainer to update their public key you can add a gpg configuration that will enable gpg to use the PGP signature

echo "allow-weak-digest-algos" >> /etc/gnupg/gpg.conf

And then retry with debsig-verify.

LVM Resize – reduce the size of one logical volume to enable expanding another

I’m running an Ubuntu workstation and when setting it up simply went the “next-next-next” route when setting up the encrypted disk via LVM. The default is to create a 1G swap partition which is just not enough when you attempt to run too many things and locks up and/or crashes the machine.

My goal was to reduce my /root partition and then use that space to extend my swap partition.

Ensure that you back up your data first! There is no guarantee when executing the following operations, even correctly, that you will not lose your data.

Decreasing the size of an LVM partition

Because I need to modify the /root partition I first needed to boot to a live image or rescue image. I happened to have a Debian 12 iso on a flash drive and after sorting out how to get my laptop to boot from it chose the rescue option when booting.

One of the unexpected options during boot to the rescue image was to decrypt the filesystem so I simply entered the passphase to decrypt it. If you are using a rescue image that does not include that feature checkout this post for details on how to decrypt it.

An overview of the steps is as follows

  1. Run a filesystem check on your filesystem
  2. Resize the filesystem contained in the logical volume
  3. Resize the logical volume

Run a filesystem check

Since you have booted from a rescue disk you first need to activate the volume group to work with it

vgchange -ay

Enter the following to the details for the volume group

root@marge:~# vgdisplay
  --- Volume group ---
  VG Name               marge-vg
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  3
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               <465.27 GiB
  PE Size               4.00 MiB
  Total PE              119108
  Alloc PE / Size       119108 / <465.27 GiB
  Free  PE / Size       0 / 0   
  VG UUID               m06QqY-lQ3N-Y0be-j7nw-V1jo-hHwK-1OrE6U

Then use lvdisplay to see the details of all of the logical volumes contained within that group

root@marge:~# lvdisplay
  --- Logical volume ---
  LV Path                /dev/marge-vg/root
  LV Name                root
  VG Name                marge-vg
  LV UUID                820Zm6-zOcV-9gMu-efCr-jnVv-nmzX-T04zFq
  LV Write Access        read/write
  LV Creation host, time marge, 2022-12-15 14:58:30 -0500
  LV Status              available
  # open                 1
  LV Size                <464.31 GiB
  Current LE             118863
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:1
   
  --- Logical volume ---
  LV Path                /dev/marge-vg/swap_1
  LV Name                swap_1
  VG Name                marge-vg
  LV UUID                MHyNcx-ASX7-bsDd-Wto3-k9u8-QjZA-Z85yIR
  LV Write Access        read/write
  LV Creation host, time marge, 2022-12-15 14:58:30 -0500
  LV Status              available
  # open                 2
  LV Size                980.00 MiB
  Current LE             245
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:2

Run the filesystem check as you cannot resize a filesystem that in a bad state. The LV Path is the path to the underlying file system.

e2fsck -fy /dev/marge-vg/root

Shrink the filesystem

Just to be safe, we will shrink the filesystem to be smaller than the target logical volume size. If we accidentally shrink the logical volume to be smaller than the filesystem that it contains this can result in corruption of your data. Once the operation is complete, we can reclaim that “lost” space. The last argument in the command specifies the desired target size of the filesystem.

resize2fs /dev/marge-vg/root 430G

Shrink the logical volume

Once the filesystem is shrunk we can now shrink the logical volume. Again, we will specify a specific target size for the logical volume of 434G. You will get a warning that the operation could result in data loss. If we have performed the previous steps successfully we are (relatively) safe to proceed.

lvreduce -L 434G /dev/marge-vg/root

lvdisplay should now show the smaller logical volume

root@marge:~# lvdisplay
  --- Logical volume ---
  LV Path                /dev/marge-vg/root
  LV Name                root
  VG Name                marge-vg
  LV UUID                820Zm6-zOcV-9gMu-efCr-jnVv-nmzX-T04zFq
  LV Write Access        read/write
  LV Creation host, time marge, 2022-12-15 14:58:30 -0500
  LV Status              available
  # open                 1
  LV Size                <434.00 GiB  <-- New LV Size
  Current LE             118863
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:1

Now that the logical volume has been reduced we can reclaim the additional space (the difference between the reduced filesystem size and the new logical volume size) by extending the filesystem to use all available space in the logical volume.

resize2fs /dev/marge-vg/root

Extend the other logical volume

Verify the amount of free space now available in the volume group

root@marge:~# vgdisplay
  --- Volume group ---
  VG Name               marge-vg
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  3
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               <465.27 GiB
  PE Size               4.00 MiB
  Total PE              119108
  Alloc PE / Size       111427 / <435.27 GiB
  Free  PE / Size       7618 / 30.00 GiB  <-- Free space
  VG UUID               m06QqY-lQ3N-Y0be-j7nw-V1jo-hHwK-1OrE6U

Run the following command to extend the logical volume. In this case, we will extend the swap volume by 30 G

lvextend -L +30G /dev/marge-vg/swap_1

Resize the filesystem for the extended logical volume

We have only resized the logical volume. The filesystem contained therein has not yet been resized to use the additional space

Because we are resizing a swap partition we need to do things a little differently. Run the following to recreate the swap partition in this logical volume

mkswap /dev/marge-vg/swap_1

For a “normal” partition you would run the following to extend the filesystem to the size of the logical volume.

resize2fs /dev/<vol-group>/<lv>

From here you should be able to reboot your system with your new LVM configuration.

Convert a Slice of Any Type to a CSV in Go

There are times, mostly for logging and debugging, when you have a slice of a given type that you would like to print to a log file or just convert to a CSV of values.

A quick and easy way to convert a slice to a CSV in Golang is to use the json module to Marshal it a JSON encoded array.

For example

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	ints := []int64{1, 2, 3, 4}
	intsCSV, _ := json.Marshal(ints)
	fmt.Printf("ints = %s\n", intsCSV)

	floats := []float64{3.14, 6.47}
	floatsCSV, _ := json.Marshal(floats)
	fmt.Printf("floats = %s\n", floatsCSV)
}

Earthly Cheat Sheet

I have just recently started using Earthly and the following is a list of commands that I want to keep track of

Caching

Completely clearing the cache: Earthly stores artifacts in docker volumes. If you want to completely flush that data and start fresh run the following

docker stop earthly-buildkitd && \
docker rm earthly-buildkitd && \
docker volume rm earthly-cache

git Cheat Sheet

A handful of handy git commands that I don’t use all that often but want to keep track of:

Stashing

Stash a single file

git stash push -m 'message here' -- path/to/file

Drop a specific stash

First figure out the id of the stash you want to drop with git stash list, then issue the following command

git stash drop stash@{n}

Add all untracked files across the entire repository

git add -A

Editing Commits

Changing author of already pushed commit

If you need to change the author of a commit that has already pushed, interactive rebase is your friend

First, determine a commit on top of which you want to rebase the commit, or a series of commits. I usually just use the commit that the series of commits is based off of (the root of the branch).

In that case you can just rebase it against that branch (but the hash of a commit before the commit you want to edit works just as well)

git rebase -i main

Your editor will open and you will be able to mark the commit that you want to edit. Change the pick token for that commit to edit and save:quit from your editor. Git will then start rebasing each commit onto the branch/commit you specified and will stop when it reaches the commit you flagged for editing.

At this point, issue the following command (updating the specific email address and author name)

git commit --amend --author="Ryan Chapin <rchapin@nbinteractive.com>" --no-edit

And then issue the commit to continue rebasing the remainder of the commits.

Undo last commit and keep changes

Sometimes you mistakenly commit things that you are not ready to commit. Run the following

git reset --soft HEAD~1

This command will

  1. git reset: move the current branch’s pointer (HEAD) to a specific commit
  2. --soft: ensure that the changes from the undone commit are preserved in your staging area and working directory as uncommitted local modifications.
  3. HEAD~1: is the reference to the commit just before the current one – the parent of HEAD

gitk

gitk is a GUI git client that has a lot of nice features and is easy to use

See orphaned branches and commits

Run gitk with the following command. Running git log with the same arguments will enable you to see the same commits, but it is much easier to browse and see with gitk.

gitk --all --reflog &

Diffing

Viewing more or less context with git diff

The default number of lines displayed above and below a change is three. To increase or decrease the number of lines displayed in the diff use the -U flag as follows, which will display 10 lines before and after the changes.

git diff -U10

Finding changes to a specific file

git log --follow --patch -- <file-path>

Reverting

When reverting your un-staged changes to your current branch you are checking out the revision from the base branch.

Reverting Changes in an entire subdirectory

git checkout <base-branch> -- path/to/directory

Changing URLs

Changing the remote URL

git remote set-url origin ssh://git@<host>:<path>/REPOSITORY.git

Use printf to join an array in Bash

If you would like to join an array of elements with a defined delimiter in Bash there is an easy way to go about it by using printf. Following is an example

#!/bin/bash

declare -a arr=()

for i in `seq 1 5`
do
  arr=("${arr[@]}" $i)
done

# Generate a single string joined by a comma.  The printf string can contain
# any arbitrary delimiter.
printf -v joined '%s,' "${arr[@]}"

# Print out the string minus the trailing comma
echo "${joined%,}"

Specifying a commit in go.mod instead of a local replace for development

Sometimes you are making changes to a dependency in another of your go projects and instead of adding a replace command in the go.mod file you want to update that entry in go.mod to point to a specific commit in the repo.

To do so, all that you need to do is:

  1. Get the git commit that you want included in your build
  2. Change directories to the same directory that your project’s go.mod file resides in which you want to refer to the library with the specific commit
  3. Run the following command which will automatically update your go.mod file with the correct “version” for your dependency go get github.com/rchapin/rlog@<commit-hash>

Using Microsoft PowerRename to Rename Batches of Files

If you have to rename a large number of files under Windows it is very tedious to do it one-by-one via the gui. Instead of writing a batch file, Microsoft has a suite of tools called PowerToys. PowerToys installs a utility called PowerRename that will do the job.

I did this under Windows 10, but I imagine that it is the same in Windows 11 based on the documentation on the PowerToys installation page.

Installation

  1. Start a PowerShell as an admin user
    1. Click on Start and search for powershell
    2. Right click on Windows PowerShell (not PowerShell ISE) and select Run as administrator
  2. Enter the following command to install PowerToys: winget install Microsoft.PowerToys --source winget
  3. It will download the installed, check the hash of the binary, and execute the install. When I did it one of those “Do you want this app to make changes to this computer” prompts showed up in the task bar that I did not see right away and I had to click on the shield to maximize it and click OK to continue the installation.
  4. After the installation finished there was a “Welcome to PowerToys” window that provided some information about the package.
  5. I am not 100% sure if this is required, but I scrolled down until I found the PowerRename entry in the left-hand side bar and clicked on it.

Usage

Once installed navigate to the directory that contains the directory of files that you want to modify.

It is a good idea to make a backup of the folder before you make changes to it in-case something goes wrong and you want to start over. Simply right-click on the directory and select Send to -> Compressed (zipped) folder to create a zip file.

  1. Right-click on the folder that contains the files and select PowerRename
  2. In the dialog box that appears enter the string that you want to change in the Search for field, and enter the string to which you want it changed in the Replace with field
  3. There are additional settings that enable you to apply the change to just the filename and/or the extension
  4. The files that will be mutated are listed in the right-hand side of the dialog box
  5. Click apply and PowerRename will execute the configured changes on the files

Docker Cheat Sheet

Following are a number of my commonly used docker commands for my own reference

Building

Run the following in the same directory in which your Dockerfile resides

docker build -t <image-name>:<version> .

Or you can specify the path to the Dockerfile

docker build . -t <image-name>:<version> -f /path/to/Dockerfile

Running

Run a container interactively

Especially useful when debugging commands that you will encapsulate in a Docker file, this will enable you to run a base image and then execute commands interactively without having to continuously run docker build . to test things out. Note the -u root to start the interactive session as the root user. If you just want to get on the container and look around you can omit this argument.

docker run -it -u root <image:tag> <shell-in-image>

Copy files to and from a running container

Assuming that you already have the container running, first run docker ps to get the id of the container

# Copy file from local file system into the container
docker cp <file-path> <container-id>:<target-path-in-container>

# Copy file from container to local file system
docker cp <container-id>:<target-path-in-container> <file-path-on-local-filesystem>

Purging images and volumes

Remove all unused or dangling images, containers, and networks. The -a will remove any stopped containers and unused images

docker system prune [-a]

To remove dangling volumes you must run a docker volume rm <volume-name> for each volume you want to remove. Running docker volume prune does not remove unused volumes.

docker volume ls | tail -n +2 | awk '{print $2}' | xargs docker volume rm

Saving an image to a tarball

docker save -o my_image.tar my_image:latest