Hello doas

Today, I switched my workstation from sudo to doas. I’m running Void Linux, and the process was fairly easy.

First, I needed to figure out how to remove sudo (yes, I realize I could have installed doas first, then removed sudo, but I decided to do it the hard way.) As it turns out, the advanced usage section of the XBPS manual details how to use the ignorepkg entry in xbps.d with nothing other than this exact use case! I created the file /etc/xbps.d/20-ignorepkg-sudo.conf with contents

ignorepkg=sudo

and then ran sudo xbps-remove sudo (an ironic command).

After that, because I was stupid and removed sudo before I had set up doas, I had to use plain-old su to change to the root user and run xi opendoas. I also configured doas in /etc/doas.conf with the following:

# see doas.conf(5) for configuration details
permit nopass keepenv :admin

I ran groupadd admin, usermod -aG admin joel, and then logged out so that my user account would see the new group perms.

And just like that, I can now run doas xbps-install ... and all of my other commands, just substituting doas for sudo.

The one thing I immediately missed was sudoedit. Before I accidentally tried to use sudo for the first time, I had already accidentally tried to run sudoedit at least 5 times. I had to fix this. I saw a discussion on Reddit where one user suggested writing a script to replace the sudoedit functionality. I quickly starting hacking together something like that. I started with:

#!/bin/sh
mkdir -p /tmp/doasedit
doas cp $1 /tmp/doasedit/tmp_file
$EDITOR /tmp/doasedit/tmp_file

And quickly ran into my first road-block. The script is going to have to change the permissions of that file before the user can edit it. But if the script changes the permissions, how can I restore it to the original location with the right permissions? cp /tmp/doasedit/tmp_file $1 won’t work. I thought about just using cat to overwrite the file contents in-place (cat /tmp/doasedit/tmp_file > $1). That could create some issues if a program has the file open. Instead, a better option is to create two copies of the file–one for editing, and one for preserving file attributes:

#!/bin/sh
mkdir -p /tmp/doasedit
doas cp $1 /tmp/doasedit/edit
doas chown -R $USER:$USER /tmp/doasedit/edit
doas cp $1 /tmp/doasedit/file
$EDITOR /tmp/doasedit/edit
cat /tmp/doasedit/edit | doas tee /tmp/doasedit/file 1>/dev/null
doas mv -f /tmp/doasedit/file $1
rm -rf /tmp/doasedit

Of course, the issue with this is that it only works with absolute paths. I want to make it work for relative paths as well. I’m going to take advantage of realpath, which is part of the coreutils package from Void. As a bonus, this will also take care of the edge case where the given file is a symlink (IIRC, sudoedit didn’t follow symlinks, so I may be diverging here):

#!/bin/sh
mkdir -p /tmp/doasedit
srcfile="$(realpath $1)"

doas cp $srcfile /tmp/doasedit/edit
doas chown -R $USER:$USER /tmp/doasedit/edit
doas cp $srcfile /tmp/doasedit/file

$EDITOR /tmp/doasedit/edit

cat /tmp/doasedit/edit | doas tee /tmp/doasedit/file 1>/dev/null
doas mv -f /tmp/doasedit/file $srcfile

rm -rf /tmp/doasedit

At this point, it works…okay-ish. It can only be used in one instance currently since I hard-coded /tmp/doasedit/file and /tmp/doasedit/edit, but that’s easily fixed:

#!/bin/sh

destfile_pfx="$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32)"

while [ -d "/tmp/doasedit/$destfile_pfx" ]; do
	destfile_pfx="$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32)"
done

mkdir -p /tmp/doasedit/$destfile_pfx
srcfile="$(realpath $1)"

doas cp $srcfile /tmp/doasedit/$destfile_pfx/edit
doas chown -R $USER:$USER /tmp/doasedit/$destfile_pfx/edit
doas cp $srcfile /tmp/doasedit/$destfile_pfx/file

$EDITOR /tmp/doasedit/$destfile_pfx/edit

cat /tmp/doasedit/$destfile_pfx/edit | doas tee /tmp/doasedit/$destfile_pfx/file 1>/dev/null
doas mv -f /tmp/doasedit/$destfile_pfx/file $srcfile

rm -rf /tmp/doasedit/$destfile_pfx

At this point, the only thing missing is the check to see if the file was actually edited:

...
cat /tmp/doasedit/$destfile_pfx/edit | doas tee /tmp/doasedit/$destfile_pfx/file 1>/dev/null

if cmp -s "/tmp/doasedit/$destfile_pfx/file" "$srcfile"; then
	echo "Skipping write; no changes."
else
	doas mv -f /tmp/doasedit/$destfile_pfx/file $srcfile
fi
...

I put this in a repo on GitHub if anyone is interested. I know that a major weakness of this script is the number of times it calls doas, which could break flows where password is required every time doas is run.