Debian

Installer Inside QEMU nographic

This ought to have been obvious, but I’ve never seen it written down anywhere. Debian uses GNU screen to provide multiple panes on the installer interface (the TUI, shells, logs, &c); screen uses Ctrl-A as its command prefix. When QEMU is multiplexing the guest serial stream and its monitor, it also uses Ctrl-A as its command prefix. You’ll thus need to use Ctrl-A Ctrl-A 2 to switch to the shell in the installer, for example.

Upgrade With Low Disk Space

The official recommendation is to grab additional storage and temporarily put /var/cache/apt/archives there. Something like the following may also help; the goal here is to upgrade the packages that are already marked as manually installed so that other packages do not get so marked. This may reduce the disk requirements for a subsequent upgrade.

apt-mark showmanual > amsm
apt-get --just-print upgrade \
  | awk '/^Conf/{ print $2 }' \
  | (while read pkg; do grep -e "^$pkg\$" amsm; done) \
  | (while read pkg; do sudo apt-get -y install $pkg; sudo apt-get clean; done)

QEMU Virtio 9P Root

Now there’s a phrase, isn’t it? This is going to give us a chroot-like experience, whereby files are directly accessible on the host (though probably best not done concurrently with the guest running) but the system is running inside qemu. This avoids the need to make disk images on the host and may make it easier to maintain long-term?

To do this properly, we’d build a custom debian-installer, but that’s really excessive just to see if it works. So, to fake it, we’re going to cheat. Go grab the debian netinst kernel (vmlinux or vmlinuz) and initramfs (initrd.gz) files from, e.g. https://www.debian.org/releases/stretch/debian-installer/. List the initramfs contents and figure out which kernel version is packaged therein:

KREV=$(zcat initrd.gz | cpio -it 2>/dev/null | sed -ne 's/lib\/modules\/\(.*\)\/kernel/\1/p' | head -n 1)
echo ${KREV}

Go grab the corresponding Debian kernel image and unpack it:

dget linux-image-${KREV}
dpkg --extract linux-image-${KREV}*.deb kernel

Now, append the 9p kernel modules to the initramfs archive; the virtio modules are, thankfully, already included. Note that we don’t extract the archive and repack it because it contains device nodes, and mknod might be beyond our reach.

gunzip initrd.gz
(cd kernel; find lib -name fscache\* -o name 9p\* | cpio -o --format newc --append -F ../initrd)
gzip initrd

And then boot the installer’s kernel and our modified initramfs and a 9P virtio device; priority=low boots the installer to expert mode:

qemu-system-... ... \
  -initrd initrd.gz -kernel vmlinux -append priority=low \
  -fsdev local,id=root,security_model=mapped-xattr,path=$PWD/root \
  -device virtio-9p-pci,fsdev=root,mount_tag=root

Walk the installer to the point of partitioning disks, which will fail to detect any and “Finish setting up partitions”, which will fail. Now, at the installer shell, mount the system:

depmod
modprobe 9p
modprobe 9pnet_virtio
mkdir /target
mount -t 9p root /target

While ordinarily the installer will balk at the idea of not configuring the disks, since we are running in expert mode, you should be back at the main menu and should be able to select “Install base system” and go from there. The installer will go through almost everything now just fine.

When asked, tell the system to build a “generic” initramfs because otherwise it’s going to fail to find the modules for the root filesystem. After that, press enter repeatedly to finish the installation. Before shutting down, run, at the installer shell:

echo 9p           >> /target/etc/initramfs-tools/modules
echo 9pnet_virtio >> /target/etc/initramfs-tools/modules
echo virtio_pci   >> /target/etc/initramfs-tools/modules

sed -i -e 's/^MODULES=.*$/MODULES=list/' /target/etc/initramfs-tools/initramfs.conf

mount -o rbind /sys  /target/sys
mount -o rbind /proc /target/proc
mount -o rbind /dev  /target/dev
chroot /target update-initramfs -k all -u

Then go ahead and halt the installer.

When booting the system next, you’ll tell qemu to boot from the in-filesystem kernel and initramfs; unfortunately, symlinks are mapped to plaintext files, so there’s a little indirection, and the initramfs needs to be told what’s going on (via the -append option here):

qemu-system-.. ... \
  -initrd "$PWD/root/boot/$(cat $PWD/root/boot/initrd.img)" \
  -kernel "$PWD/root/boot/$(cat $PWD/root/boot/vmlinux)" \
  -append "rootfstype=9p root=root rw" \
  -fsdev local,id=root,security_model=mapped-xattr,path=$PWD/root \
  -device virtio-9p-pci,fsdev=root,mount_tag=root

Ubuntu

Live CD Persistence By Default

Sometimes we want to boot systems persistently by default. Yes, yes, we’re weird. Grab the ISO; for the purposes of this exercise, we used lubuntu-15.10-desktop-amd64.iso. Extract the boot menu configuration:

osirrox -indev lubuntu-15.10-desktop-amd64.iso -cdi /isolinux -extract_single ./txt.cfg txt.cfg

Apply this diff to remove the install option and append persistent:

--- txt.cfg.orig    2015-10-21 12:39:29.000000000 -0400
+++ txt.cfg 2016-02-03 04:37:12.442310613 -0500
@@ -2,11 +2,7 @@
 label live
   menu label ^Try Lubuntu without installing
   kernel /casper/vmlinuz.efi
-  append  file=/cdrom/preseed/lubuntu.seed boot=casper initrd=/casper/initrd.lz quiet splash ---
-label live-install
-  menu label ^Install Lubuntu
-  kernel /casper/vmlinuz.efi
-  append  file=/cdrom/preseed/lubuntu.seed boot=casper only-ubiquity initrd=/casper/initrd.lz quiet splash ---
+  append  file=/cdrom/preseed/lubuntu.seed boot=casper initrd=/casper/initrd.lz quiet splash persistent ---
 label check
   menu label ^Check disc for defects
   kernel /casper/vmlinuz.efi

Repack the ISO image:

xorriso -indev lubuntu-15.10-desktop-amd64.iso -outdev custom.iso -boot_image isolinux keep -cdi /isolinux -cpr txt.cfg .

At boot, now, the livecd will scan for (among other options) a filesystem whose label is casper-rw; you can make such a thing by running, within the live system, for example:

mkfs.ext4 -L casper-rw /dev/sda1

File Transfer

Help, all I have is a shell

Pipe the output of this to your shell. This is exceptionally not fast

hexdump -e '"echo -e '\''" 120/1 "Y%03o" "'Z\'' >> xetc.tgz\n"' xetc.tgz \
  | sed 's;Y;\\0;g;s/Z/\\c/;s/\\0 *\\0/\\c/;1s/>/ /'

SLIP

Ah, the Serial Line Internet Protocol. Here’s a worked example. On the host:

slattach -L -l -n -s 38400 -p slip /dev/tty_dgrp_a2_6
ifconfig sl0 172.29.8.1 pointopoint 172.29.8.2
echo 1 > /proc/sys/net/ipv4/conf/sl0/forwarding
iptables ...

On the guest:

slattach -L -d -m -p slip -s 38400 /dev/ttySC1 &
ifconfig sl0 172.29.8.2 pointopoint 172.29.8.1

ZFS

Large File Deletion

ZFS would occasionally stall a machine if you ask it to delete a large file from a deduplicated data set as it would have engage in huge transactions involving the DDT. Now that https://github.com/zfsonlinux/zfs/issues/3725 is fixed, this should almost certainly not matter; the code here is for historical reference only.

You can hold its hand and slow the process down, which should eliminate IO stalls:

for i in `seq $(($(stat -f %z $FILE)/1024/1024)) -1 1`; do  \
        echo $i; \
        truncate -s $((i*1024*1024)) $FILE; \
        sync; \
        sleep 5; \
done; \
rm $FILE

UNIX Shell

Software Watchdog With runit

runit / daemontools / s6 and friends can be used as a kind of watchdog to detect the absence of some event. Use sv restart ... or the equivalent to tickle the watchdog and delay the event, perhaps in another supervised script!

  • ./run creates a new process group:

    #!/bin/sh
    exec setsid ./run2
    
  • ./run2 traps SIGTERM to exit and waits for a timeout before invoking the watchdog response:

    #!/bin/sh
    onTerm() { trap '' TERM; kill -TERM 0; exit; }
    trap onTerm TERM
    
    sleep $(cat ./watchdog-period) &
    wait
    trap '' TERM
    exec ./watchdog-fire
    

    Where ./watchdog-period contains the interval in seconds during which the watchdog must be reset, and ./watchdog-fire is the script to run when it’s all gone south.

Line-based Parallel Mapping in Shell

“I was nerd-sniped” is really the only defense I have for this particular section’s existence. It’s something of a response to http://catern.com/posts/pipes.html, which goes into a fair bit of detail and to moderate lengths to work around not having message boundaries respected by UNIX pipes. That approach didn’t sit well with me, aesthetically, and I guess I was wanting to get to know my shell better.

Anyway, given a shell pipeline

source | mapper | sink

if mapper’s action is dependent only on each line, but takes a while, you might wish to have multiple mappers running in parallel. For various reasons, existing tools like GNU parallel and xargs are not sufficient (as of this writing). Thus, I present fanout.sh which uses zsh coproc-esses and the zsh/zselect to manage a flock of workers.

Use as in this self-test:

diff <(./fanout.sh 3 cat < /usr/share/dict/words | sort) <(sort /usr/share/dict/words)

If we pass a slow worker, we can see the right thing happening as the pipes first fill to their internal buffer sizes and then things start blocking:

$ ./fanout.sh 3 ./scat.sh < /usr/share/dict/words
A
Iroquoian's
Sutton's
A's
Iroquois
Suva
AA's

Git

Packing Milestones

Left to its own devices, git will occasionally repack the entire repository, coalescing all objects into a single pack. This entails repacking old revisions every time, which, for large repositories, is unlikely to do anyone any good. You can create “kept” pack files containing objects that will not be reconsidered in the future, reducing the churn. A good way to do this is to pick a “milestone” commit, something that certainly won’t go away in the future, and pack everything transitively reachable therefrom. In zsh, this might be as simple as

(B=origin/master P=.git/objects/pack/pack H=$(git pack-objects --honor-pack-keep --revs $P <<< $B) ; echo "$B $(date +%Y%m%d)" > $P-$H.keep)

This command can be rerun whenever the churn set gets too large. If this is the only source of kept packs (git does not create them by default), then the repository has the nice property that its set of kept packs are transitively closed, which (hopefully) will become a useful fact in future versions of git.

Kerberos & AFS

Persistent AFS Tokens for Daemons

If a daemon runs as a particular UID, it’s relatively straightforward to use the AFS Unix CM’s default UID-based PAG to ensure that that service has authenticated access to AFS, even without that UID having access to the Kerberos keytab. For example, using runit and k5start, a run script of

#!/bin/sh
exec keyctl session - $PWD/run2

creates a new “session” kernel keyring and runs run2

#!/bin/sh
export KRB5CCNAME=KEYRING:session
export AKLOG=$PWD/aklog.sh
exec k5start -F -P -K 240 -f /etc/krb5/service.keytab -U -t

which configures Kerberos to use said session keyring for ticket storage and uses k5start to get tickets from the service.keytab file. The aklog.sh script then enters the target UID PAG, by setting its UID, but brings the session and environment variables with it:

#!/bin/sh
exec chpst -u ${service_uid} aklog

Replace ${service_uid} appropriately.