wren-linux / tools / test-vps-client.sh
#!/bin/bash
# Checks tools/vps-client.sh in a temp home: the onion client key is a real
# X25519 pair (its public half is what openssl derives from the private one),
# kept for you alone, in the form setup-vps.sh takes; Tor's client file and the
# ssh entry say what they should; tor-auth runs no sudo. setup, with pretend
# programs: it says what it will do and asks first, installs Tor's tools only
# on yes, never reaches the server but through Tor, confirms from a second
# login, gives Tor the client key, keeps your own ssh entries, closes port 22
# through the onion. Changes nothing outside a temp folder. Run by test.sh,
# or: bash tools/test-vps-client.sh
# shellcheck disable=SC2016 # the inner shells expand their own variables
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
V=$HERE/tools/vps-client.sh
T=$(mktemp -d) || { echo " FAIL couldn't make a temp folder (is /tmp full?)"; exit 1; }
trap 'rm -rf "$T"' EXIT
fails=0
check() { # DESCRIPTION CONDITION-COMMAND...
local what=$1
shift
if "$@" >/dev/null 2>&1; then printf ' ok %s\n' "$what"; else printf ' FAIL %s\n' "$what"; fails=$((fails + 1)); fi
}
not() { ! "$@"; }
command -v openssl >/dev/null || { echo " ok skipped: openssl isn't installed"; exit 0; }
export HOME=$T/home XDG_CONFIG_HOME=
mkdir -p "$HOME" "$T/bin"
printf '#!/bin/sh\necho "sudo $*" >>"%s/sudo-calls"\n' "$T" >"$T/bin/sudo"
chmod +x "$T/bin/sudo"
vc() { PATH="$T/bin:$PATH" bash "$V" "$@"; }
K=$HOME/.config/wren/vps
pub=$(vc --keygen | tail -n1)
check "keygen: a public half setup-vps.sh takes (52 of A-Z and 2-7)" bash -c '[[ $1 =~ ^[A-Z2-7]{52}$ ]]' _ "$pub"
check "...the private half kept for you alone" bash -c '[ "$(stat -c %a "$1")" = 700 ] && [ "$(stat -c %a "$1/wren-vps.key")" = 600 ] && [[ $(cat "$1/wren-vps.key") =~ ^[A-Z2-7]{52}$ ]]' _ "$K"
derived=$(python3 -I -c 'import base64,sys; k=sys.argv[1]; sys.stdout.buffer.write(bytes.fromhex("302e020100300506032b656e04220420")+base64.b32decode(k+"===="))' "$(cat "$K/wren-vps.key")" |
openssl pkey -inform DER -pubout -outform DER 2>/dev/null | python3 -I -c 'import base64,sys; print(base64.b32encode(sys.stdin.buffer.read()[-32:]).decode().rstrip("="))')
check "...and the two belong together (openssl derives that public half from the private one)" test "$derived" = "$pub"
check "...a second keygen with the same name is refused (the key isn't replaced)" not vc --keygen
check "keygen-show prints the public half again" test "$(vc --keygen-show)" = "$pub"
grep -q '\[\[ \$VPS_ONION_SSH_CLIENT =~ \^\[A-Z2-7\]{52}\$ \]\]' "$HERE/tools/setup-vps.sh"
check "...the same form setup-vps.sh checks for" test $? -eq 0
ON=$(printf 'k%.0s' {1..56})
out=$(vc --tor-auth "$ON.onion")
check "tor-auth: Tor's client file, for that address, yours alone" bash -c '
[ "$(cat "$1/wren-vps.auth_private")" = "$2:descriptor:x25519:$(cat "$1/wren-vps.key")" ] && [ "$(stat -c %a "$1/wren-vps.auth_private")" = 600 ]' _ "$K" "$ON"
check "...and the root commands that give it to this computer's Tor, printed, not run" bash -c '
grep -q "ClientOnionAuthDir /var/lib/tor/onion_auth" <<<"$1" && grep -q "sudo install -m 0600 -o debian-tor" <<<"$1" && [ ! -e "$2/sudo-calls" ]' _ "$out" "$T"
check "...an address that isn't one is refused" not vc --tor-auth example.com
out=$(vc --ssh-config wren "$ON")
check "ssh-config: through this computer's Tor, with the server's key file and your user" bash -c '
grep -q "^Host wren-vps$" <<<"$1" && grep -q "HostName $2.onion" <<<"$1" && grep -q "User wren" <<<"$1" &&
grep -q "ProxyCommand nc -X 5 -x 127.0.0.1:9050 %h %p" <<<"$1" && grep -q "IdentitiesOnly yes" <<<"$1"' _ "$out" "$ON"
check "...a user name that isn't one is refused" not vc --ssh-config 'x;y' "$ON"
check "a name for the key files that isn't one is refused" not vc --keygen '../x'
check "...and the entry keeps the server's key under its name (the first login's check counts for it)" grep -qx " HostKeyAlias wren-vps" <<<"$out"
# setup: pretend ssh, scp, nc, sudo and dpkg-query, each writing down how it was called
SITE_ON=$(printf 's%.0s' {1..56})
mkdir -p "$T/fake"
cat >"$T/fake/ssh" <<EOF
#!/bin/bash
echo "ssh \$*" >>"$T/calls"
# (a login whose command holds what $T/ssh-fail says fails, as one that closes would)
if [ -s "$T/ssh-fail" ] && [[ "\$*" == *"\$(cat "$T/ssh-fail")"* ]]; then exit 255; fi
case "\$*" in
# git through ssh to the mirror: an empty one here, as setup-vps.sh makes it
*"git-upload-pack "*) exec git-upload-pack "$T/mirror.git" ;;
*"wren-vps --status"*) printf 'User: wren\r\nOnion site: $SITE_ON.onion\r\nOnion SSH: $ON.onion\r\n' ;;
*"hostname -I"*) echo "203.0.113.9 10.0.0.2 " ;;
esac
exit 0
EOF
printf '#!/bin/sh\necho "scp $*" >>"%s/calls"\n' "$T" >"$T/fake/scp"
cat >"$T/fake/nc" <<'EOF'
#!/bin/sh
case "$1" in -h) echo ' [-w timeout] [-X proxy_protocol] [-x proxy_address[:port]]'; exit 1 ;; esac
exit 0
EOF
# sudo: written down; it reads and edits only the test's own files (WREN_ETC), and
# journalctl says Tor connected
cat >"$T/fake/sudo" <<EOF
#!/bin/bash
echo "sudo \$*" >>"$T/calls"
last=\${!#}
case \$1 in
sed | grep) case \$last in "$T"/*) exec "\$@" ;; esac ;;
tee) case \$last in "$T"/*) shift; exec tee "\$@" ;; *) cat >/dev/null ;; esac ;;
journalctl) echo "Bootstrapped 100% (done): Done" ;;
esac
exit 0
EOF
# torsocks -i ssh-keyscan: the server's key, as the next line of $T/hostkeys says (one a call)
cat >"$T/fake/torsocks" <<EOF
#!/bin/bash
echo "torsocks \$*" >>"$T/calls"
n=\$(grep -c '^torsocks ' "$T/calls")
k=\$(sed -n "\${n}p" "$T/hostkeys"); [ -n "\$k" ] || k=\$(tail -n1 "$T/hostkeys")
echo "\${@: -1} \$k"
EOF
# dpkg-query: installed are the packages named in $T/installed
printf '#!/bin/sh\nfor p; do :; done\nif grep -qx "$p" "%s/installed"; then echo "install ok installed"; else exit 1; fi\n' "$T" >"$T/fake/dpkg-query"
# sleep: written down, and no wait (the countdown of a change the server undoes)
printf '#!/bin/sh\necho "sleep $*" >>"%s/calls"\n' "$T" >"$T/fake/sleep"
# ssh-agent: its number is a sleep's of the test's, to see that it is stopped; ssh-add: written down
cat >"$T/fake/ssh-agent" <<EOF
#!/bin/bash
echo "ssh-agent \$*" >>"$T/calls"
/bin/sleep 300 >/dev/null 2>&1 &
echo \$! >"$T/agent-pid"
echo "SSH_AUTH_SOCK=$T/agent.sock; export SSH_AUTH_SOCK; SSH_AGENT_PID=\$!; export SSH_AGENT_PID;"
EOF
printf '#!/bin/sh\necho "ssh-add $*" >>"%s/calls"\n' "$T" >"$T/fake/ssh-add"
chmod +x "$T/fake"/*
# vs ARGS...: setup, its answers from stdin (one a line), as the shared tty_read takes them
export WREN_ETC=$T/etc
mkdir -p "$WREN_ETC/tor"
vs() { cat >"$T/answers"; WREN_TEST_ANSWERS=$T/answers PATH="$T/fake:$PATH" bash "$V" --setup "$@" 2>&1; }
vb() { cat >"$T/answers"; WREN_TEST_ANSWERS=$T/answers PATH="$T/fake:$PATH" bash "$V" --bridges 2>&1; }
ssh-keygen -q -t ed25519 -N '' -C server -f "$T/hk1" && ssh-keygen -q -t ed25519 -N '' -C other -f "$T/hk2"
HK1=$(cut -d' ' -f1,2 "$T/hk1.pub")
HK2=$(cut -d' ' -f1,2 "$T/hk2.pub")
echo "$HK1" >"$T/hostkeys"
mkdir -p "$HOME/.ssh"
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl wren-vps' >"$HOME/.ssh/wren-vps.pub"
: >"$T/calls"
printf 'tor\nnetcat-openbsd\ntorsocks\n' >"$T/installed"
out=$(echo n | vs 203.0.113.9)
check "setup: says what it is about to do, and asks first; no: nothing is done" bash -c '
grep -q "every connection" <<<"$1" && grep -q "Continue?" <<<"$1" && grep -q "Nothing was done" <<<"$1" && [ ! -s "$2" ]' _ "$out" "$T/calls"
: >"$T/calls"
printf 'tor\n' >"$T/installed"
out=$(printf 'y\nn\n' | vs 203.0.113.9)
check "...and before it asks: stay at the keyboard (a login waits only about a minute), and what to do if it stops" bash -c '
a=$(grep -n "DO NOT GO AFK" <<<"$1" | cut -d: -f1) && b=$(grep -n "Continue?" <<<"$1" | cut -d: -f1) && [ "$a" -lt "$b" ] &&
grep -q "one minute to answer each SSH key passphrase prompt" <<<"$1" && grep -q "five minutes to answer each sudo password prompt" <<<"$1" &&
grep -q "counts down ten minutes, then offers to try again" <<<"$1"' _ "$out"
check "...a missing netcat-openbsd: asked before it is installed; no: nothing installed, no login" bash -c '
grep -q "sudo apt install netcat-openbsd" <<<"$1" && ! grep -q "apt-get" "$2" && ! grep -q "^ssh\|^scp" "$2"' _ "$out" "$T/calls"
: >"$T/calls"
out=$(printf 'y\ny\nn\ny\n' | vs 203.0.113.9)
check "...yes: installed with apt (torsocks too, for the key check)" grep -q "^sudo apt-get install -y netcat-openbsd torsocks$" "$T/calls"
check "...before each login it says which secret it asks: the SSH key's passphrase, then wren's password for sudo" bash -c '
[ "$(grep -c "It asks: your SSH key.s passphrase (for ~/.ssh/wren-vps)" <<<"$1")" -ge 5 ] &&
grep -q "It asks: your SSH key.s passphrase (for ~/.ssh/wren-vps), then wren.s password on the server (for sudo): the one you just gave it" <<<"$1"' _ "$out"
check "...the passphrase once instead (ssh-agent) was offered; no: no agent, and each login asks it" bash -c '
grep -q "Type your SSH key.s passphrase once now" <<<"$1" && ! grep -q "^ssh-agent\|^ssh-add" "$2"' _ "$out" "$T/calls"
check "...the server's key fetched three times, each through a Tor connection of its own" test "$(grep -c "^torsocks -i ssh-keyscan -T 60 -t ed25519 203.0.113.9$" "$T/calls")" = 3
check "...the same each time: kept for wren-vps, before any login" bash -c '
grep -qxF "wren-vps $2" "$1" && [ "$(grep -n "^torsocks" "$3" | tail -n1 | cut -d: -f1)" -lt "$(grep -n "^scp" "$3" | cut -d: -f1)" ]' _ "$HOME/.ssh/known_hosts" "$HK1" "$T/calls"
check "...and every login takes only that key, never asking about another" bash -c '
! grep -E "^(ssh|scp) .*203.0.113.9" "$1" | grep -vqF -- "-o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes"' _ "$T/calls"
check "...it shows the key's fingerprint, the same through three paths" bash -c 'grep -qF "the same through three Tor paths: $(ssh-keygen -lf "$2" | cut -d" " -f2)" <<<"$1"' _ "$out" "$T/hk1.pub"
check "...setup-vps.sh copied to root, through Tor, with this key only" grep -qF "scp -q -o ProxyCommand=nc -X 5 -x 127.0.0.1:9050 %h %p -o IdentitiesOnly=yes -i $HOME/.ssh/wren-vps -o HostKeyAlias=wren-vps" "$T/calls"
check "...and run as root, with your keys and the onion services, through Tor" bash -c '
l=$(grep "^ssh -t .* root@203.0.113.9 " "$1") && grep -qF "ProxyCommand=nc -X 5 -x 127.0.0.1:9050" <<<"$l" &&
grep -qF "VPS_ADMIN=wren VPS_SSH_KEY=ssh-ed25519\\ AAAA" <<<"$l" && grep -qE "VPS_ONION_SSH_CLIENT=[A-Z2-7]{52} bash setup-vps.sh$" <<<"$l"' _ "$T/calls"
check "...confirmed from a second login, as your user, through Tor" bash -c '
grep "^ssh -t .* wren@203.0.113.9 " "$1" | grep -qF "ProxyCommand=nc -X 5 -x 127.0.0.1:9050" && grep "wren@203.0.113.9" "$1" | grep -q "sudo wren-vps --confirm"' _ "$T/calls"
check "...no login ever goes to the address without Tor" bash -c '! grep -E "^(ssh|scp) " "$1" | grep "203.0.113.9" | grep -vqF "ProxyCommand=nc -X 5 -x 127.0.0.1:9050 %h %p"' _ "$T/calls"
check "...this computer's Tor gets the client key for the SSH onion address" bash -c '
grep -q "^sudo install -m 0600 -o debian-tor -g debian-tor .*/wren-vps.auth_private /var/lib/tor/onion_auth/$" "$1" &&
grep -q "^sudo systemctl restart tor$" "$1" && grep -q "^$2:descriptor:x25519:" "$3/wren-vps.auth_private"' _ "$T/calls" "$ON" "$K"
check "...the ssh entry is written, between its marks, to the onion address" bash -c '
grep -qx "# >>> wren-vps (tools/vps-client.sh) >>>" "$1" && grep -qx " HostName $2.onion" "$1" && [ "$(stat -c %a "$1")" = 600 ]' _ "$HOME/.ssh/config" "$ON"
check "...and the publish settings, with the site's onion address" bash -c '[ "$(cat "$1")" = "$(printf "VPS_HOST=wren-vps\nVPS_SITE_ONION=%s.onion" "$2")" ]' _ "$K/site.conf" "$SITE_ON"
check "...then port 22 closed through the onion login, confirmed from a second one" bash -c '
grep -n "" "$1" | grep -q "ssh -t wren-vps sudo wren-vps --ssh-onion-only" && grep -q "^ssh -t wren-vps sudo wren-vps --confirm$" "$1" &&
[ "$(grep -n "ssh -t wren-vps sudo wren-vps --ssh-onion-only" "$1" | cut -d: -f1)" -lt "$(grep -n "^ssh -t wren-vps sudo wren-vps --confirm$" "$1" | cut -d: -f1)" ]' _ "$T/calls"
check "...and a summary: ssh, scp, rsync, what to back up" bash -c '
grep -q "ssh wren-vps " <<<"$1" && grep -q "scp FILE wren-vps:PATH" <<<"$1" && grep -q "rsync -a FOLDER/" <<<"$1" && grep -q "Back these up" <<<"$1"' _ "$out"
: >"$T/calls"
printf 'Host other\n HostName example.org\n' >>"$HOME/.ssh/config"
printf 'y\ny\n' | vs 203.0.113.9 >/dev/null
check "setup again: its entry replaced, not doubled, and yours kept" bash -c '
[ "$(grep -c "^Host wren-vps$" "$1")" = 1 ] && grep -qx "Host other" "$1" && [ -f "$1.wren-bak" ]' _ "$HOME/.ssh/config"
printf 'Host wren-vps\n HostName mine.example\n' >"$HOME/.ssh/config"
out=$(printf 'y\ny\n' | vs 203.0.113.9)
check "...a Host wren-vps of your own: left as it is, and the entry shown instead" bash -c '
[ "$(cat "$1")" = "$(printf "Host wren-vps\n HostName mine.example")" ] && grep -q "of your own already" <<<"$2" && grep -q "HostName $3.onion" <<<"$2"' _ "$HOME/.ssh/config" "$out" "$ON"
rm -f "$HOME/.ssh/config"
: >"$T/calls"
printf 'y\ny\n' | vs 203.0.113.9 --login debian >/dev/null
check "--login debian: the provider's user, setup-vps.sh through its sudo" bash -c '
grep "^ssh -t .* debian@203.0.113.9 " "$1" | grep -q "sudo env VPS_ADMIN=wren " && grep -q "debian@203.0.113.9:setup-vps.sh" "$1"' _ "$T/calls"
# the confirming login fails: why, the 10 minutes counted down, then the offer to run again
echo "wren-vps --confirm" >"$T/ssh-fail"
: >"$T/calls"
# (one answer a question: Continue, install, the agent, then no to running again)
out=$(printf 'y\ny\nn\nn\nn\n' | vs 203.0.113.9)
rm -f "$T/ssh-fail"
check "a confirming login that fails: says why, and why to wait (the server undoes the change itself)" bash -c '
grep -q "Not confirmed: the login closed" <<<"$1" && grep -q "puts its old SSH and firewall settings back by itself" <<<"$1"' _ "$out"
check "...counts the minutes down, 10 to 1, a minute each" bash -c '
grep -q "run it again in 10 minutes\." <<<"$1" && grep -q "run it again in 1 minute\." <<<"$1" && [ "$(grep -c "^sleep 60$" "$2")" = 10 ]' _ "$out" "$T/calls"
check "...then offers to run the same command again; no: it ends there, nothing more done" bash -c '
grep -q "Run it again now (bash .*vps-client.sh --setup 203.0.113.9)?" <<<"$1" && ! grep -q "^sudo install -m 0600" "$2"' _ "$out" "$T/calls"
# A gong before each prompt, offered only with your own sound server and the timer's gong
mkdir -p "$T/fake-sound" "$T/sounds/alarms"
: >"$T/sounds/alarms/gong.wav"
printf '#!/bin/sh\necho "pw-play $*" >>"%s/calls"\n' "$T" >"$T/fake-sound/pw-play"
# (systemctl --user is-active pipewire.socket: yes, the sound server runs)
printf '#!/bin/sh\nexit 0\n' >"$T/fake-sound/systemctl"
chmod +x "$T/fake-sound"/*
vg() { cat >"$T/answers"; WREN_SOUND_DIR=$T/sounds WREN_TEST_ANSWERS=$T/answers PATH="$T/fake-sound:$T/fake:$PATH" bash "$V" --setup 203.0.113.9 2>&1; }
: >"$T/calls"
out=$(printf 'y\ny\ny\nn\ny\n' | vg)
/bin/sleep 1
check "with the sound extra: it offers a gong before each prompt; yes: the gong (not a sharper sound), each time" bash -c '
grep -q "A gong each time it waits for you" <<<"$1" && [ "$(grep -c "^pw-play $2/sounds/alarms/gong.wav$" "$3")" -ge 5 ] &&
! grep "^pw-play" "$3" | grep -qv gong.wav' _ "$out" "$T" "$T/calls"
: >"$T/calls"
out=$(printf 'y\nn\ny\nn\ny\n' | vg)
/bin/sleep 1
check "...no: not a sound" bash -c '! grep -q "^pw-play" "$1"' _ "$T/calls"
rm "$T/sounds/alarms/gong.wav"
: >"$T/calls"
out=$(printf 'y\ny\nn\ny\n' | vg)
check "...no gong among the timer's sounds: no offer (and no sharper sound instead)" bash -c '! grep -q "A gong" <<<"$1" && ! grep -q "^pw-play" "$2"' _ "$out" "$T/calls"
# pw-play there, the gong too, but no sound server of yours running (systemctl says no)
: >"$T/sounds/alarms/gong.wav"
mkdir -p "$T/fake-nosound"
cp "$T/fake-sound/pw-play" "$T/fake-nosound/"
printf '#!/bin/sh\nexit 3\n' >"$T/fake-nosound/systemctl"
chmod +x "$T/fake-nosound"/*
: >"$T/calls"
out=$(printf 'y\ny\nn\ny\n' >"$T/answers"; WREN_SOUND_DIR=$T/sounds WREN_TEST_ANSWERS=$T/answers PATH="$T/fake-nosound:$T/fake:$PATH" bash "$V" --setup 203.0.113.9 2>&1)
check "without your own sound server running (pw-play alone isn't enough): no offer, no sound" bash -c '! grep -q "A gong" <<<"$1" && ! grep -q "^pw-play" "$2"' _ "$out" "$T/calls"
# the passphrase once, into an ssh-agent of this run's own
: >"$T/calls"
out=$(printf 'y\ny\n' | vs 203.0.113.9)
check "the agent of this run: started for 45 minutes at most, the key put in, before the first login" bash -c '
grep -q "^ssh-agent -s -t 45m$" "$1" && a=$(grep -n "^ssh-add $2/.ssh/wren-vps$" "$1" | cut -d: -f1) &&
b=$(grep -n "^scp" "$1" | cut -d: -f1) && [ "$a" -lt "$b" ]' _ "$T/calls" "$HOME"
check "...then no prompt says the passphrase again, only what else is asked" bash -c '
[ "$(grep -c "It asks: your SSH key.s passphrase" <<<"$1")" = 1 ] && grep -q "It asks: wren.s password on the server (for sudo): the one you just gave it" <<<"$1"' _ "$out"
check "...and at the end it is stopped (the key in no agent any more), and says so" bash -c '
! kill -0 "$(cat "$1")" 2>/dev/null && grep -q "ssh-agent is stopped" <<<"$2"' _ "$T/agent-pid" "$out"
mv "$HOME/.ssh/known_hosts" "$T/kh"
printf '%s\n%s\n%s\n' "$HK1" "$HK2" "$HK1" >"$T/hostkeys"
: >"$T/calls"
out=$(printf 'y\ny\n' | vs 203.0.113.9)
check "the key differs on one of the three paths: stops, nothing sent to the server" bash -c '
grep -q "Three Tor paths gave the server.s key differently" <<<"$1" && ! grep -q "^ssh\|^scp" "$2" && [ ! -e "$3" ]' _ "$out" "$T/calls" "$HOME/.ssh/known_hosts"
echo "wren-vps $HK2" >"$HOME/.ssh/known_hosts"
echo "$HK1" >"$T/hostkeys"
: >"$T/calls"
out=$(printf 'y\ny\n' | vs 203.0.113.9)
check "...another key kept for wren-vps already (an earlier server): stops, and says how to clear it" bash -c '
grep -q "ssh-keygen -R wren-vps" <<<"$1" && ! grep -q "^ssh\|^scp" "$2"' _ "$out" "$T/calls"
# the key kept as Debian's ssh keeps it, its name hashed (HashKnownHosts): the same key goes on
echo "wren-vps $HK1" >"$HOME/.ssh/known_hosts"
ssh-keygen -H -f "$HOME/.ssh/known_hosts" >/dev/null 2>&1
rm -f "$HOME/.ssh/known_hosts.old"
: >"$T/calls"
printf 'y\ny\n' | vs 203.0.113.9 >/dev/null
check "...the same key, kept with its name hashed (Debian's ssh does): taken, and the setup goes on" bash -c '
! grep -q "wren-vps ssh-ed25519" "$1" && grep -q "^scp" "$2"' _ "$HOME/.ssh/known_hosts" "$T/calls"
mv "$T/kh" "$HOME/.ssh/known_hosts"
mv "$HOME/.ssh/wren-vps.pub" "$T/pub"
: >"$T/calls"
out=$(printf 'y\n' | vs 203.0.113.9)
check "no ~/.ssh/wren-vps (the key the provider has): stops before any login" bash -c 'grep -q "There is no ~/.ssh/wren-vps" <<<"$1" && ! grep -q "^ssh\|^scp" "$2"' _ "$out" "$T/calls"
mv "$T/pub" "$HOME/.ssh/wren-vps.pub"
check "an address that isn't one is refused" not vs 'example.org;x' </dev/null
# --reconnect: this computer again (a reinstall), from the keys put back; nothing on the server
# changes. (Its ssh: as the setup's, and a login that may keep a new key, accept-new, keeps HK1.)
mkdir -p "$T/fake2"
cat >"$T/fake2/ssh" <<EOF
#!/bin/bash
case "\$*" in *StrictHostKeyChecking=accept-new*) echo "wren-vps \$(cut -d' ' -f1,2 "$T/hk1.pub")" >>"$HOME/.ssh/known_hosts" ;; esac
exec "$T/fake/ssh" "\$@"
EOF
chmod +x "$T/fake2/ssh"
vr() { cat >"$T/answers"; WREN_TEST_ANSWERS=$T/answers PATH="$T/fake2:$T/fake:$PATH" bash "$V" --reconnect "$@" 2>&1; }
printf 'tor\nnetcat-openbsd\ntorsocks\n' >"$T/installed"
cp "$HOME/.ssh/known_hosts" "$T/kh.saved" 2>/dev/null || : >"$T/kh.saved"
cp "$K/site.conf" "$T/site.saved"
check "--reconnect: a user or an address that isn't one is refused" bash -c '! "$@" root "$ON" </dev/null && ! "$@" wren example.org </dev/null && ! "$@" </dev/null' _ vr
rm -f "$HOME/.ssh/wren-vps"
: >"$T/calls"
out=$(echo y | vr wren "$ON")
check "...the SSH key not put back yet: says which files, and does nothing" bash -c '
grep -q "Put back the key you backed up" <<<"$1" && ! grep -qE "^(ssh|sudo) " "$2"' _ "$out" "$T/calls"
echo "a private key" >"$HOME/.ssh/wren-vps"
mv "$K/wren-vps.key" "$T/client.key"
out=$(echo y | vr wren "$ON")
check "...the onion client key not put back: says so (and what losing it means), does nothing" bash -c '
grep -q "no onion client key here" <<<"$1" && grep -q "provider.s web console" <<<"$1" && ! grep -qE "^(ssh|sudo) " "$2"' _ "$out" "$T/calls"
mv "$T/client.key" "$K/wren-vps.key"
chmod 644 "$K/wren-vps.key" "$HOME/.ssh/wren-vps"
out=$(echo n | vr wren "$ON")
check "...both back: says what it will do (and that the server isn't changed), asks; no: nothing done" bash -c '
grep -q "The server isn.t changed" <<<"$1" && grep -q "Continue?" <<<"$1" && grep -q "Nothing was done" <<<"$1" && [ ! -s "$2" ]' _ "$out" "$T/calls"
# the server's key put back too (known_hosts as it was backed up)
ssh-keygen -R wren-vps -f "$HOME/.ssh/known_hosts" >/dev/null 2>&1
echo "wren-vps $HK1" >>"$HOME/.ssh/known_hosts"
: >"$T/calls"
out=$(printf 'y\ny\nn\n' | vr wren "$ON")
check "...yes: the keys made yours alone again" bash -c '[ "$(stat -c %a "$1")" = 600 ] && [ "$(stat -c %a "$2")" = 600 ]' _ "$K/wren-vps.key" "$HOME/.ssh/wren-vps"
check "...this computer's Tor gets the client key for that address" bash -c '
grep -q "^sudo install -m 0600 -o debian-tor -g debian-tor $1/wren-vps.auth_private /var/lib/tor/onion_auth/$" "$2" &&
grep -q "^$3:descriptor:x25519:" "$1/wren-vps.auth_private"' _ "$K" "$T/calls" "$ON"
check "...the ssh entry, to the onion address, for your user" bash -c '
sed -n "/^# >>> wren-vps/,/^# <<< wren-vps/p" "$1" | grep -q "HostName $2.onion" && sed -n "/^# >>> wren-vps/,/^# <<< wren-vps/p" "$1" | grep -q "User wren"' _ "$HOME/.ssh/config" "$ON"
check "...the server's key put back: the login takes only that one" grep -q '^ssh -n -o StrictHostKeyChecking=yes wren-vps true$' "$T/calls"
check "...and shows its fingerprint" grep -qF "$(ssh-keygen -lf "$T/hk1.pub" | cut -d' ' -f2)" <<<"$out"
check "...nothing on the server is changed (no setup, no port 22, nothing sent but the login)" bash -c '
! grep -qE "setup-vps|--ssh-onion-only|--confirm|--domain|^scp " "$1" && [ "$(grep -c "^ssh " "$1")" = 1 ]' _ "$T/calls"
check "...the publish settings put back: kept as they were, not fetched" bash -c 'cmp -s "$1" "$2" && ! grep -q "wren-vps --status" "$3"' _ "$K/site.conf" "$T/site.saved" "$T/calls"
# no known_hosts line (not backed up): the key the onion address answers with is kept
ssh-keygen -R wren-vps -f "$HOME/.ssh/known_hosts" >/dev/null 2>&1
rm -f "$K/site.conf"
: >"$T/calls"
out=$(printf 'y\ny\nn\ny\n' | vr wren "$ON")
check "...no server key put back: it says the onion address vouches for the one kept, before asking" bash -c '
a=$(grep -n "onion address vouches" <<<"$1" | cut -d: -f1) && b=$(grep -n "Continue?" <<<"$1" | cut -d: -f1) && [ "$a" -lt "$b" ]' _ "$out"
check "...the login keeps it (accept-new, only then), and its fingerprint is shown" bash -c '
grep -q "^ssh -n -o StrictHostKeyChecking=accept-new wren-vps true$" "$1" && grep -qF "$(ssh-keygen -lf "$2" | cut -d" " -f2)" <<<"$3"' _ "$T/calls" "$T/hk1.pub" "$out"
check "...publish settings not put back: asked, and fetched from the server, through the onion entry" bash -c '
grep -q "^ssh -t wren-vps sudo wren-vps --status$" "$1" && [ "$(cat "$2")" = "$(printf "VPS_HOST=wren-vps\nVPS_SITE_ONION=%s.onion" "$3")" ]' _ "$T/calls" "$K/site.conf" "$SITE_ON"
check "...and a summary: ssh, scp, publishing, --origin for a new git copy" bash -c 'grep -q "ssh wren-vps" <<<"$1" && grep -q "publish-site.sh" <<<"$1" && grep -q -- "--origin" <<<"$1"' _ "$out"
cp "$T/kh.saved" "$HOME/.ssh/known_hosts"
cp "$T/site.saved" "$K/site.conf"
rm -f "$HOME/.ssh/wren-vps"
# --backup: what this computer needs to reach the server again, one file encrypted with a
# passphrase, on a stick; --reconnect --from puts it back. Real gpg; ssh -G and sudo cat answer
# as on a computer set up with --setup.
BP='a long passphrase for the backup'
cat >"$T/fake2/sudo" <<EOF
#!/bin/bash
case "\$1 \$2" in "cat $T/"*) echo "sudo \$*" >>"$T/calls"; exec cat "\$2" ;; esac
# sudo -n: root's folders "there" are those named in rootdirs; no-sudo: sudo not given yet
case "\$1 \$2 \$3" in
"-n test -d") [ ! -e "$T/no-sudo" ] && grep -qxF "\$4" "$T/rootdirs" 2>/dev/null; exit ;;
esac
case "\$1 \$2" in "-n true") [ ! -e "$T/no-sudo" ]; exit ;; esac
exec "$T/fake/sudo" "\$@"
EOF
cat >"$T/fake2/ssh-g" <<EOF
#!/bin/bash
if [ "\$1" = -G ]; then printf 'user wren\nhostname $ON.onion\nport 22\n'; exit 0; fi
exec "$T/fake2/ssh" "\$@"
EOF
mv "$T/fake2/ssh" "$T/fake2/ssh-real"
sed -i "s|exec \"$T/fake2/ssh\"|exec \"$T/fake2/ssh-real\"|" "$T/fake2/ssh-g"
mv "$T/fake2/ssh-g" "$T/fake2/ssh"
chmod +x "$T/fake2/"*
vk() { cat >"$T/answers"; WREN_TEST_ANSWERS=$T/answers PATH="$T/fake2:$T/fake:$PATH" bash "$V" "$@" 2>&1; }
STICK=$T/stick
mkdir -p "$STICK"
echo "the private key" >"$HOME/.ssh/wren-vps"
chmod 600 "$HOME/.ssh/wren-vps"
ssh-keygen -R wren-vps -f "$HOME/.ssh/known_hosts" >/dev/null 2>&1
echo "wren-vps $HK1" >>"$HOME/.ssh/known_hosts"
printf 'UseBridges 1\nClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy\nBridge obfs4 192.0.2.7:443 0123456789ABCDEF0123456789ABCDEF01234567 cert=abcd iat-mode=0\n' >"$WREN_ETC/tor/torrc"
mkdir -p "$HOME/notstick"
out=$(printf 'n\n' | vk --backup "$HOME/notstick")
check "--backup: a folder on this computer is asked about (the backup would go with it); no by default: nothing written" bash -c '
grep -q "not on a stick" <<<"$1" && grep -q "anyway" <<<"$1" && [ -z "$(ls "$2")" ]' _ "$out" "$HOME/notstick"
# (the pretend stick is a temp folder, on this computer: yes, there anyway)
out=$(printf 'y\ny\n%s\n%s\n' "$BP" "not the same one at all" | vk --backup "$STICK")
check "...two passphrases that don't match: nothing written" bash -c 'grep -q "didn.t match" <<<"$1" && [ -z "$(ls "$2")" ]' _ "$out" "$STICK"
out=$(printf 'y\ny\nshort\nshort\n' | vk --backup "$STICK")
check "...a short one: nothing written" bash -c 'grep -q "At least 20" <<<"$1" && [ -z "$(ls "$2")" ]' _ "$out" "$STICK"
: >"$T/calls"
# (a git copy of yours with no server: its commits are nowhere else)
git init -q -b main "$T/gitcopy" && git -C "$T/gitcopy" -c user.name=t -c user.email=t@t commit -q --allow-empty -m mine
out=$(printf 'y\ny\n%s\n%s\n' "$BP" "$BP" | WREN_GIT_COPY=$T/gitcopy vk --backup "$STICK")
B1=$(find "$STICK" -maxdepth 1 -name "wren-vps-backup-*.gpg" | head -n1)
check "...one file on the stick, named by the date, yours alone" bash -c '[ -n "$1" ] && [[ ${1##*/} =~ ^wren-vps-backup-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}\.gpg$ ]] && [ "$(stat -c %a "$1")" = 600 ]' _ "$B1"
check "...encrypted: none of the keys, the address or the bridges readable in it" bash -c '
! grep -qaF "the private key" "$1" && ! grep -qaF "$(cat "$2")" "$1" && ! grep -qaF "$3" "$1" && ! grep -qaF "192.0.2.7" "$1"' _ "$B1" "$K/wren-vps.key" "$ON"
mkdir -p "$T/open" "$T/opengpg" && chmod 700 "$T/opengpg"
gpg --homedir "$T/opengpg" --batch --quiet --pinentry-mode loopback --passphrase "$BP" --decrypt "$B1" 2>/dev/null | tar -C "$T/open" -xf - 2>/dev/null
check "...it opens with the passphrase: the keys, your user and address, the server's key, the settings, the bridges" bash -c '
cmp -s "$1/wren-vps" "$2/.ssh/wren-vps" && cmp -s "$1/client.key" "$3/wren-vps.key" && grep -qx "USER=wren" "$1/server" &&
grep -qx "ONION=$4.onion" "$1/server" && grep -qF "$5" "$1/known_hosts" && cmp -s "$1/site.conf" "$3/site.conf" &&
grep -qx "TYPE=obfs4" "$1/bridges" && grep -q "^obfs4 192.0.2.7:443 " "$1/bridges"' _ "$T/open" "$HOME" "$K" "$ON" "$(cut -d" " -f2 <<<"$HK1")"
gpgconf --homedir "$T/opengpg" --kill all 2>/dev/null
check "...the bridges read through sudo (only root reads Tor's settings), after asking" grep -q "^sudo cat $WREN_ETC/tor/torrc$" "$T/calls"
check "...nothing left in memory (/dev/shm) afterwards" bash -c '[ -z "$(find /dev/shm -maxdepth 1 -name "wren-vps.*" -user "$(id -un)" 2>/dev/null)" ]'
check "...and it says how to put it back" grep -q -- "--reconnect --from $STICK" <<<"$out"
# (the list, as plain text: each line ends in the colour's reset)
reminder() { sed -n "/backs up your server keys only/,/Check each backup opens/p" <<<"$1" | sed 's/\x1b\[[0-9;]*m//g'; }
r=$(reminder "$out")
check "...then, in red, what else to back up before wiping: KeePassXC always (none found: a command to fill in)" bash -c '
grep -q "$(printf "\033")\[1;31m.*backs up your server keys only" <<<"$1" && grep -q "KeePassXC databases (.kdbx files: none found" <<<"$2" &&
grep -qxF " \$ cp PATH/TO/YOURS.kdbx $3/" <<<"$2"' _ "$out" "$r" "$STICK"
check "...each on its own line, with the command that backs it up on the next" bash -c '
n=$(grep -c "^ - " <<<"$1"); [ "$n" -ge 2 ] && [ "$(grep -A1 "^ - " <<<"$1" | grep -c "^ \$ ")" = "$n" ]' _ "$r"
check "...a git copy with no server: named (its commits are nowhere else), with its bundle into the backup folder" bash -c '
grep -A1 -F "$2/gitcopy: it has no server" <<<"$1" | grep -qxF " \$ git -C $2/gitcopy bundle create $3/gitcopy.bundle --all"' _ "$r" "$T" "$STICK"
check "...and nothing this computer doesn't have (no signing key, vault, wallet, gpg key, onion keys, documents, backup extra)" bash -c '
! grep -qiE "signing key|vault|wallet|gnupg|onion|your files:|other SSH keys|backup extra" <<<"$1"' _ "$r"
# the same, with a vault, a wallet, gpg and SSH keys, and documents of yours here
mkdir -p "$HOME/.local/share/wren-vault" "$HOME/.electrum" "$HOME/Documents" "$HOME/Pictures" "$HOME/.gnupg/private-keys-v1.d" "$T/stick3" \
"$HOME/.local/share/wren/signing-key" "$HOME/.config/wren"
: >"$HOME/.local/share/wren-vault/gocryptfs.conf"
echo 0123456789ABCDEF0123456789ABCDEF01234567 >"$HOME/.local/share/wren/signing-key/fingerprint"
: >"$HOME/.config/wren/backup.conf"
echo /var/lib/tor/wren-onion-site >"$T/rootdirs"
: >"$HOME/Documents/letter.txt"
: >"$HOME/Passwords.kdbx"
mkdir -p "$HOME/.local/share/libvirt/images" && echo "a disk" >"$HOME/.local/share/libvirt/images/Whonix-Workstation.qcow2"
: >"$HOME/.gnupg/private-keys-v1.d/k.key"
: >"$HOME/.ssh/id_ed25519"
out=$(printf 'y\ny\n%s\n%s\n' "$BP" "$BP" | WREN_GIT_COPY=$T/gitcopy vk --backup "$T/stick3")
r=$(reminder "$out")
check "...with them here: the vault, the wallet's seed words and files, gpg and SSH keys, the documents (only the folders with files)" bash -c '
grep -q "your vault" <<<"$1" && grep -q "seed words on paper, and their files (.electrum)$" <<<"$1" && grep -q "gpg keys" <<<"$1" &&
grep -q "other SSH keys" <<<"$1" && grep -q "your files (~/Documents)$" <<<"$1"' _ "$r"
check "...the KeePassXC databases it found, named in the command" grep -qxF " \$ cp $HOME/Passwords.kdbx $T/stick3/" <<<"$r"
check "...the vault closed, then copied (it is encrypted by itself)" grep -qxF " \$ vault close && cp -a ~/.local/share/wren-vault $T/stick3/" <<<"$r"
SYM="gpg --symmetric --cipher-algo AES256 --s2k-mode 3 --s2k-digest-algo SHA512 --s2k-count 65011712"
check "...what isn't encrypted by itself (wallets, gpg, SSH and onion keys) into a file gpg encrypts (as strongly as the backup), never as it is" bash -c '
grep -qxF " \$ tar -C ~ -c .electrum | $3 -o $2/wallets.tar.gpg" <<<"$1" &&
grep -qxF " \$ tar -C ~ -c .gnupg | $3 -o $2/gnupg.tar.gpg" <<<"$1" &&
grep -qxF " \$ sudo tar -C /var/lib/tor -c wren-onion-site | $3 -o $2/wren-onion-site.tar.gpg" <<<"$1"' _ "$r" "$T/stick3" "$SYM"
check "...the Whonix Workstation (a wallet may be in it): seed words, or its disk switched off; the disk itself never read" bash -c '
grep -q "the Whonix Workstation, with any wallet in it" <<<"$1" &&
grep -qF "\$ whonix stop && cp --sparse=always $2/.local/share/libvirt/images/Whonix-Workstation.qcow2 $3/" <<<"$1" &&
[ "$(cat "$2/.local/share/libvirt/images/Whonix-Workstation.qcow2")" = "a disk" ]' _ "$r" "$HOME" "$T/stick3"
R=$T/stick3/RESTORE.txt
check "RESTORE.txt on the stick: --reconnect --from first, then each item with how it was backed up and how it goes back" bash -c '
a=$(grep -n "reconnect --from $2" "$1" | cut -d: -f1) && b=$(grep -n "your vault" "$1" | cut -d: -f1) && [ "$a" -lt "$b" ] &&
[ "$(grep -c "^ backed up with: " "$1")" = "$(grep -c "^ put back with: " "$1")" ] && [ "$(grep -c "^ - " "$1")" -ge 6 ] &&
grep -qxF " put back with: gpg -d $2/wallets.tar.gpg | tar -C ~ -x (after the wallet extras are installed again)" "$1" &&
grep -q "put back with: wrenctl install onion-site, then: sudo systemctl stop tor" "$1"' _ "$R" "$T/stick3"
check "...text to read, never a key, a passphrase or the onion address in it" bash -c '
[ ! -x "$1" ] && ! grep -qF "$2" "$1" && ! grep -qF "$3" "$1" && ! grep -qF "the private key" "$1" && ! grep -qF "$(cat "$4/wren-vps.key")" "$1"' _ "$R" "$BP" "$ON" "$K"
check "...and the screen says where it is" grep -qF "How to put each back: $T/stick3/RESTORE.txt" <<<"$out"
# the commands it shows, run as shown (gpg pretended: it writes what it is given to its -o file)
mkdir -p "$T/fakegpg"
printf '#!/bin/sh\n[ "$1" = -d ] && exec cat "$2"\nwhile [ $# -gt 0 ]; do [ "$1" = -o ] && { cat >"$2"; exit 0; }; shift; done\nexit 1\n' >"$T/fakegpg/gpg"
chmod +x "$T/fakegpg/gpg"
for c in "cp -a ~/Documents" "tar -C ~ -c .electrum" "git -C $T/gitcopy bundle"; do
l=$(grep -F "\$ $c" <<<"$r" | head -n1); l=${l#*\$ }
(cd "$HOME" && HOME=$HOME PATH="$T/fakegpg:$PATH" bash -c "$l") >/dev/null 2>&1
done
check "...and they work as shown: the documents copied, the wallet packed, the git copy bundled" bash -c '
[ -e "$1/Documents/letter.txt" ] && tar -tf "$1/wallets.tar.gpg" | grep -q "^.electrum" && git bundle verify -q "$1/gitcopy.bundle"' _ "$T/stick3"
# the wallet gone (a reinstall), then put back as RESTORE.txt says
echo "my wallet" >"$T/stick3/marker" && rm -rf "$HOME/.electrum"
l=$(sed -n "s/^ put back with: \(gpg -d .*wallets.tar.gpg | tar -C ~ -x\).*/\1/p" "$R" | head -n1)
(cd "$HOME" && HOME=$HOME PATH="$T/fakegpg:$PATH" bash -c "$l") >/dev/null 2>&1
check "...and RESTORE.txt's way back works: the wallet's folder is back where it was" test -d "$HOME/.electrum"
check "...a signing key made here (its fingerprint kept, whether the extra is installed or not), the backup extra's home" bash -c '
grep -q "signing key" <<<"$1" && grep -q "backup extra" <<<"$1"' _ "$r"
check "...an onion site's keys, root's, seen with sudo given in this run (and remote access's, not there, left out)" bash -c '
grep -q "your onion site.s keys (/var/lib/tor/wren-onion-site" <<<"$1" && ! grep -q "remote access" <<<"$1"' _ "$r"
check "...asked without a prompt (sudo -n), and only to look" bash -c '! grep -E "^sudo " "$1" | grep -v -e "^sudo cat " -e "^sudo -n test -d /var/lib/tor/" -e "^sudo -n true$" | grep -q "test\|true"' _ "$T/calls"
: >"$T/no-sudo"
mkdir -p "$T/stick4"
out=$(printf 'y\nn\n%s\n%s\n' "$BP" "$BP" | WREN_GIT_COPY=$T/gitcopy vk --backup "$T/stick4")
r=$(reminder "$out")
check "...root's folders it can't look in (no sudo given): listed anyway, \"if you set one up\" (better than a key forgotten)" bash -c '
grep -q "your onion site.s keys, if you set one up" <<<"$1" && grep -q "remote access.s onion keys, if you set one up" <<<"$1"' _ "$r"
rm -rf "$HOME/.local/share/wren-vault" "$HOME/.electrum" "$HOME/Documents" "$HOME/Pictures" "$HOME/.gnupg" "$HOME/.ssh/id_ed25519" "$T/stick3" "$T/stick4" "$HOME/Passwords.kdbx" "$HOME/.local/share/libvirt" \
"$HOME/.local/share/wren/signing-key" "$HOME/.config/wren/backup.conf" "$T/rootdirs" "$T/no-sudo"
# A reinstall: the keys, entry, server key, settings and Tor gone; Tor not installed
reinstall() {
rm -f "$HOME/.ssh/wren-vps" "$HOME/.ssh/wren-vps.pub" "$HOME/.ssh/config" "$K/wren-vps.key" "$K/wren-vps.pub" "$K/site.conf" "$K/wren-vps.auth_private"
ssh-keygen -R wren-vps -f "$HOME/.ssh/known_hosts" >/dev/null 2>&1
rm -f "$WREN_ETC"/tor/.wren-* && : >"$WREN_ETC/tor/torrc"
printf 'netcat-openbsd\ntorsocks\n' >"$T/installed"
}
reinstall
: >"$T/calls"
out=$(printf 'not the passphrase at all\n' | vk --reconnect --from "$STICK")
check "--reconnect --from the stick's folder: a wrong passphrase puts nothing back, sends nothing" bash -c '
grep -q "doesn.t open with that passphrase" <<<"$1" && [ ! -e "$2/.ssh/wren-vps" ] && ! grep -qE "^(ssh|sudo) " "$3"' _ "$out" "$HOME" "$T/calls"
# (a second, older backup in the folder: the newest is the one taken)
cp "$B1" "$STICK/wren-vps-backup-2020-01-01-0000.gpg"
: >"$T/calls"
out=$(printf '%s\ny\ny\ny\nn\n' "$BP" | vk --reconnect --from "$STICK")
check "...it says which backup it took: the newest in the folder" grep -qF "The newest backup there: ${B1##*/}" <<<"$out"
check "...your files back, yours alone" bash -c '
[ "$(stat -c %a "$1/.ssh/wren-vps")" = 600 ] && [ "$(stat -c %a "$2/wren-vps.key")" = 600 ] && [ "$(cat "$1/.ssh/wren-vps")" = "the private key" ] &&
[ -s "$2/site.conf" ]' _ "$HOME" "$K"
check "...the server's key back: the login takes only that one" bash -c '
ssh-keygen -F wren-vps -f "$1/.ssh/known_hosts" | grep -qF "$2" && grep -q "^ssh -n -o StrictHostKeyChecking=yes wren-vps true$" "$3"' _ "$HOME" "$(cut -d" " -f2 <<<"$HK1")" "$T/calls"
check "...Tor set up with the backup's bridges, not asked again" bash -c '
grep -q "^Bridge obfs4 192.0.2.7:443 " "$1" && grep -q "From your backup: " <<<"$2" && ! grep -q "Do you need a bridge" <<<"$2"' _ "$WREN_ETC/tor/torrc" "$out"
check "...the user and address from the backup: the entry goes there" bash -c '
sed -n "/^# >>> wren-vps/,/^# <<< wren-vps/p" "$1" | grep -q "HostName $2.onion"' _ "$HOME/.ssh/config" "$ON"
check "...nothing left in memory" bash -c '[ -z "$(find /dev/shm -maxdepth 1 -name "wren-vps.*" -user "$(id -un)" 2>/dev/null)" ]'
echo "another private key" >"$HOME/.ssh/wren-vps"
out=$(printf '%s\n' "$BP" | vk --reconnect --from "$B1")
check "a different key of yours already in place: not replaced, it stops" bash -c '
grep -q "isn.t the one in the backup" <<<"$1" && [ "$(cat "$2")" = "another private key" ]' _ "$out" "$HOME/.ssh/wren-vps"
# backups not made by this: one holding another name, one with a bridge line Tor would refuse
mkdir -p "$T/bad/b" "$T/bad/g" && chmod 700 "$T/bad/g"
cp "$T/open/"* "$T/bad/b/" 2>/dev/null
mkbad() { # NAME: $T/bad/b packed and encrypted as STICK2/NAME
mkdir -p "$T/stick2"
tar -C "$T/bad/b" -cf - . | gpg --homedir "$T/bad/g" --batch --quiet --pinentry-mode loopback --passphrase "$BP" --symmetric -o "$T/stick2/$1" 2>/dev/null
}
echo x >"$T/bad/b/.bashrc"
mkbad wren-vps-backup-odd.gpg
rm -f "$HOME/.ssh/wren-vps"
out=$(printf '%s\n' "$BP" | vk --reconnect --from "$T/stick2/wren-vps-backup-odd.gpg")
check "a backup holding anything else (.bashrc): refused, nothing put back" bash -c 'grep -q "isn.t a backup this made" <<<"$1" && [ ! -e "$2" ]' _ "$out" "$HOME/.ssh/wren-vps"
rm -f "$T/bad/b/.bashrc"
printf 'TYPE=obfs4\nobfs4 192.0.2.7:443 0123456789ABCDEF0123456789ABCDEF01234567 cert="x"\n' >"$T/bad/b/bridges"
mkbad wren-vps-backup-bridge.gpg
out=$(printf '%s\n' "$BP" | vk --reconnect --from "$T/stick2/wren-vps-backup-bridge.gpg")
check "...a bridge line Tor wouldn't take (checked as a typed one is): refused, nothing put back" bash -c '
grep -q "bridge line in the backup" <<<"$1" && [ ! -e "$2" ]' _ "$out" "$HOME/.ssh/wren-vps"
gpgconf --homedir "$T/bad/g" --kill all 2>/dev/null
echo "the private key" >"$HOME/.ssh/wren-vps"
printf 'tor\nnetcat-openbsd\ntorsocks\n' >"$T/installed"
cp "$T/kh.saved" "$HOME/.ssh/known_hosts"
cp "$T/site.saved" "$K/site.conf"
rm -f "$HOME/.ssh/wren-vps"
# --domain: looked up through Tor, given to the server once it points there, confirmed
printf '#!/bin/sh\necho "tor-resolve $*" >>"%s/calls"\nr=$(cat "%s/resolve" 2>/dev/null)\n[ -n "$r" ] && echo "$r"\n' "$T" "$T" >"$T/fake/tor-resolve"
printf '#!/bin/sh\nprintf 200\n' >"$T/fake/curl"
chmod +x "$T/fake/tor-resolve" "$T/fake/curl"
vd() { cat >"$T/answers"; WREN_TEST_ANSWERS=$T/answers PATH="$T/fake:$PATH" bash "$V" --domain "$@" 2>&1; }
printf 'tor\nnetcat-openbsd\ntorsocks\n' >"$T/installed"
: >"$T/calls"
: >"$T/resolve"
out=$(vd example.org </dev/null)
check "--domain not ready yet (no address): says so, and changes nothing on the server" bash -c '
grep -q "has no address yet" <<<"$1" && grep -q "^tor-resolve example.org$" "$2" && ! grep -q "VPS_DOMAIN" "$2"' _ "$out" "$T/calls"
echo 198.51.100.1 >"$T/resolve"
: >"$T/calls"
out=$(echo n | vd example.org)
check "...pointing elsewhere than the server: asked, and no: nothing changed" bash -c '
grep -q "server.s own addresses are: 203.0.113.9" <<<"$1" && ! grep -q "VPS_DOMAIN" "$2"' _ "$out" "$T/calls"
echo 203.0.113.9 >"$T/resolve"
: >"$T/calls"
out=$(vd www.example.org </dev/null)
check "...ready: the current setup-vps.sh sent, the bare name (no www.) given to the server, then confirmed" bash -c '
s=$(grep -n "^scp .*setup-vps.sh wren-vps:setup-vps.sh$" "$2" | cut -d: -f1) &&
a=$(grep -n "^ssh -t wren-vps sudo env VPS_DOMAIN=example.org bash setup-vps.sh$" "$2" | cut -d: -f1) &&
b=$(grep -n "^ssh -t wren-vps sudo wren-vps --confirm$" "$2" | cut -d: -f1) &&
[ "$s" -lt "$a" ] && [ "$a" -lt "$b" ] &&
grep -q "https://example.org/ answers" <<<"$1"' _ "$out" "$T/calls"
check "...and says what's left at the registrar (CAA, DNSSEC)" grep -q 'CAA 0 issue "letsencrypt.org"' <<<"$out"
check "...a name that isn't one is refused" not vd 'example.org;x' </dev/null
# --origin: this git copy's server becomes the server's mirror, over ssh through Tor
G=$T/copy
git init -q -b main "$G"
git init -q --bare -b main "$T/mirror.git"
vo() { cat >"$T/answers"; WREN_GIT_COPY=$G WREN_TEST_ANSWERS=$T/answers PATH="$T/fake:$PATH" bash "$V" --origin 2>&1; }
: >"$T/calls"
out=$(echo n | vo)
check "--origin: a copy with none gets the mirror, through the wren-vps entry (Tor), and it is looked at" bash -c '
[ "$(git -C "$1" remote get-url origin)" = "wren-vps:/srv/wren-git/wren-linux.git" ] &&
grep -q "^ssh .*wren-vps .*git-upload-pack .*/srv/wren-git/wren-linux.git" "$2" && grep -q "nothing yet" <<<"$3"' _ "$G" "$T/calls" "$out"
git -C "$G" remote set-url origin https://github.com/someone/wren.git
out=$(printf 'n\n' | vo)
check "...one with another server: asked first; no: left as it was" bash -c '
[ "$(git -C "$1" remote get-url origin)" = "https://github.com/someone/wren.git" ] && grep -q "Nothing was changed" <<<"$2"' _ "$G" "$out"
out=$(printf 'y\nn\n' | vo)
check "...yes: the mirror" test "$(git -C "$G" remote get-url origin)" = "wren-vps:/srv/wren-git/wren-linux.git"
check "...and it says what's next: git-push.sh (or wren-release), then publish-site.sh" bash -c '
grep -q "tools/git-push.sh" <<<"$1" && grep -q "tools/publish-site.sh" <<<"$1"' _ "$out"
# Bridges, asked as every Wren script asks them
OBFS4='obfs4 192.0.2.1:443 0123456789ABCDEF0123456789ABCDEF01234567 cert=AAAA iat-mode=0'
TR=$WREN_ETC/tor/torrc
: >"$TR"
printf 'netcat-openbsd\ntorsocks\n' >"$T/installed"
# (a computer new to this: no mark of an earlier run here)
rm -f "$WREN_ETC/tor/.wren-vps-client"
: >"$T/calls"
out=$(printf 'y\ny\ny\n1\n%s\n\ny\n' "$OBFS4" | vs 203.0.113.9)
check "setup, Tor new here: after offering it, asks whether a bridge is needed, which kind, the lines" bash -c '
grep -q "Do you need a bridge" <<<"$1" && grep -q "Kinds of bridge" <<<"$1" && grep -q "Paste your obfs4 bridge lines" <<<"$1"' _ "$out"
check "...Tor held (masked) before it is installed, with obfs4proxy for the bridge" bash -c '
m=$(grep -n "^sudo systemctl mask tor.service" "$1" | cut -d: -f1) && i=$(grep -n "^sudo apt-get install -y tor .*obfs4proxy$" "$1" | cut -d: -f1) && [ "$m" -lt "$i" ]' _ "$T/calls"
check "...the bridge in torrc, between its own marks, then Tor started" bash -c '
grep -qx "# >>> Wren Linux vps-client bridges >>>" "$1" && grep -qx "UseBridges 1" "$1" && grep -qxF "Bridge $2" "$1" &&
grep -q "^sudo systemctl unmask tor.service tor@default.service" "$3"' _ "$TR" "$OBFS4" "$T/calls"
check "...and the mark: the system Tor is needed here (OnionShare leaves it on)" bash -c '[ -s "$1/tor/.wren-vps-client" ] && grep -qw .wren-vps-client "$2"' _ "$WREN_ETC" "$HERE/lib/tor.sh"
check "...the onion client key's line goes to that torrc, once" test "$(grep -c "^ClientOnionAuthDir /var/lib/tor/onion_auth$" "$TR")" = 1
printf 'tor\nnetcat-openbsd\ntorsocks\n' >"$T/installed"
out=$(printf 'y\ny\n' | vs 203.0.113.9)
check "setup, Tor here already: no bridge question (its bridges are kept), and no second ClientOnionAuthDir" bash -c '
! grep -q "Do you need a bridge" <<<"$1" && [ "$(grep -c "^ClientOnionAuthDir" "$2")" = 1 ]' _ "$out" "$TR"
: >"$T/calls"
out=$(printf 'y\n3\n' | vb)
check "bridges: says what there is now, asks again; Snowflake replaces the obfs4 line" bash -c '
grep -q "Now: 1 bridge line" <<<"$1" && ! grep -qF "$2" "$3" && grep -q "^Bridge snowflake " "$3" && grep -qx "ClientTransportPlugin snowflake exec /usr/bin/snowflake-client" "$3"' _ "$out" "$OBFS4" "$TR"
check "...snowflake-client installed, Tor restarted, and it says Tor is connected" bash -c '
grep -q "^sudo apt-get install -y snowflake-client$" "$2" && grep -q "^sudo systemctl restart tor@default.service" "$2" && grep -q "Tor is connected, with Snowflake" <<<"$1"' _ "$out" "$T/calls"
out=$(printf 'n\n' | vb)
check "...no bridge: its lines go, the rest of torrc stays" bash -c '
! grep -q "vps-client bridges" "$1" && ! grep -q "^Bridge " "$1" && grep -q "^ClientOnionAuthDir" "$1" && grep -q "Now: [0-9]* bridge" <<<"$2"' _ "$TR" "$out"
printf '# >>> Wren Linux apt-tor >>>\nUseBridges 1\nBridge %s\n# <<< Wren Linux apt-tor <<<\n' "$OBFS4" >"$TR"
cp "$TR" "$T/torrc-before"
out=$(printf 'y\n3\n' | vb)
check "...bridges another Wren script set up: left alone, and it says where to change them" bash -c '
cmp -s "$1" "$2" && grep -q "set up by another Wren Linux script" <<<"$3"' _ "$TR" "$T/torrc-before" "$out"
[ "$fails" -eq 0 ]