From dd31339e49c28b9c719b60425c5f7ae31a0e45f4 Mon Sep 17 00:00:00 2001 From: Robert Günzler Date: Fri, 1 Jul 2022 16:50:25 +0200 Subject: os: finish implementing including encryption using sops --- .vim/lua/debugger.lua | 4 +- bin/os-conf | 136 ++++++++++++++++++++++++++++++++++---------------- os/.envrc | 1 + os/README.md | 17 +++++-- os/manifest | 48 +++++++++++++----- 5 files changed, 145 insertions(+), 61 deletions(-) create mode 100644 os/.envrc diff --git a/.vim/lua/debugger.lua b/.vim/lua/debugger.lua index e847076..32b75b5 100644 --- a/.vim/lua/debugger.lua +++ b/.vim/lua/debugger.lua @@ -64,8 +64,8 @@ dap.adapters.go_dlv_local = function(callback, config) -- {{{ end -- }}} dap.adapters.go_dlv_remote = { - type = "server", - host = "127.0.0.1", + type = 'server', + host = '127.0.0.1', port = 38697, } diff --git a/bin/os-conf b/bin/os-conf index 94f60a4..8ee143d 100755 --- a/bin/os-conf +++ b/bin/os-conf @@ -1,56 +1,108 @@ #!/bin/sh +set -e +[ -n "$DEBUG" ] && set -x + OS_DIR=${OS_DIR:-$HOME/os} OS_MANIFEST=$OS_DIR/manifest -noop="echo " -total=0 +target=/tmp/os/ +restore= +list= +while getopts r:lh opt; do + case "$opt" in + r) + restore=1 + target="$OPTARG" + ;; + l) list=1 ;; + h | ?) + printf "usage: %s [options]\n" "$(basename "$0")" + printf "\n" + printf " -l list files that would be saved\n" + printf " -r TARGET restore to TARGET (default: %s)\n" "${target}" + printf "\n" + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) -copy_file() { - path=$1 - if [ -f "$path" ]; then - # check that the file in the manifest matches - # skip if it does - if cmp "$path" "$OS_DIR$path" >/dev/null; then - return - fi - # mkdir base path - mkdir -p "$(dirname "$OS_DIR$path")" - # copy - cp -v "$path" "$OS_DIR$path" - # tally - total=$((total + 1)) - elif [ -d "$path" ]; then - # handle dir - for file in "$path"/*; do - copy_file "$file" +tmp_prefix=os-secret- +# cleanup +trap '{ rm -f "/tmp/${tmp_prefix}"*; }' EXIT + +handle_secret() { + # NOTE: we can't print anything to stdout here, only the final encrypted file + # to be included in the os archive + case "$1" in + encrypt) + # read the first line from input and use it as path + IFS=$(printf '\r') read -r filepath + archive=/tmp/"$tmp_prefix"$(printf "%s" "$filepath" | md5sum | cut -d' ' -f1) + # create tar archive from input, descends into directory + ( + printf "%s\n" "$filepath" + cat - + ) | + doas sh -c "( + tar -cz -f $archive -T - 2>/dev/null; + chown 1000:1000 $archive; + )" + # encrypt the tarfile and amend some information that makes + # reconstructing easier + ( + sops --config /dev/null --encrypt "$archive" 2>/dev/null | + jq -r '.sops.data_extension |= "tar.gz"' + ) >"${archive}.enc" + printf "%s\n" "${archive}.enc" + ;; + decrypt) + while IFS=$(printf '\r') read -r line; do + # if $(jq -r '.sops.data_extension' "$line") == "tar.gz" + sops --config /dev/null exec-file "$line" "tar xzf - -C $target < {}" done - else - # noop - echo "noop: $path" - return - fi + ;; + esac } -main() { - while IFS="\r" read -r line - do - [ -z "$line" ] && continue - echo "$line" | awk '/#.*/{exit 1}' || continue - - # check that the file in the manifest exists - if [ ! -e "$line" ]; then - echo "error: $line not found" >&2 - continue +handle_rules() { + while IFS=$(printf '\r') read -r line; do + if printf "%s" "$line" | grep -qe '#secret$'; then + printf "%s\n" "$line" | sed -e 's/\s#secret//' | handle_secret encrypt + else + # shellcheck disable=SC2086 + find "$(printf "%s\n" $line)" -print 2>/dev/null || true fi - copy_file "$line" - done + } -uniq "$OS_MANIFEST" \ - | grep -v '^$' \ - | grep -v '^#' \ - | main +_rsync() { + extra= + [ -n "$list" ] && extra="--dry-run" + rsync -v -hhh $extra \ + --archive \ + --links \ + "$@" +} -echo "$total files." +if [ -n "$restore" ]; then + printf "os-conf: restoring %s to %s\n" "${OS_DIR}" "${target}" >&2 + mkdir -p "${target}" || true + _rsync \ + --exclude '/manifest' \ + --exclude '/README.md' \ + --exclude 'colors.todo' \ + --exclude 'os-secret*.enc' \ + "${OS_DIR}/" "${target}" + find "${OS_DIR}" -type f -name 'os-secret*.enc' -print | + handle_secret decrypt +else + printf "os-conf: saving to %s\n" "${OS_DIR}" >&2 + uniq "$OS_MANIFEST" | + grep -v '^$' | + grep -v '^#' | + handle_rules | + _rsync --files-from=- "/" "${OS_DIR}/" +fi diff --git a/os/.envrc b/os/.envrc new file mode 100644 index 0000000..4cfd171 --- /dev/null +++ b/os/.envrc @@ -0,0 +1 @@ +export SOPS_PGP_FP=17AE9645C9848DA159D6643B8E8F20D25FF33529 diff --git a/os/README.md b/os/README.md index 8ca3356..80db948 100644 --- a/os/README.md +++ b/os/README.md @@ -1,8 +1,15 @@ -# os configuration +# OS configuration -TODO script that does: +use `os-conf` to save and `os-conf -r /` to restore configuration +the script reads a manifest file inside `$OS_DIR`, which defaults to `$HOME/os` -* parse manifest -* backup listed files from rootfs -* patch/add files in rootfs +## manifest syntax +lines prefixed with `#` or `$` are ignored, the magic comment `#secret`, when +appended to a line, marks the entry as containing data that shouldn't be checked +into a public repository. Instead we use [sops](https://github.com/mozilla/sops) to encrypt and store it under +`$OS_DIR/tmp/os-secret-*`. These encrypted archives are transparently handled +by the restore code. + +Encryption requires the `SOPS_PGP_FP` environment variable (or whatever is required +by any of the other supported encryption schemes). diff --git a/os/manifest b/os/manifest index debfc00..1e6047f 100644 --- a/os/manifest +++ b/os/manifest @@ -1,20 +1,44 @@ -# portage config +# portage /var/lib/portage/world -/etc/portage/* +/etc/portage +/etc/eixrc +/etc/eix-sync.conf +/etc/genup/updaters.d/10-tip.sh +/etc/genup/updaters.d/23-eclean-packages.sh -# openrc config -/etc/conf.d/* -/etc/runlevels/* - -/etc/buildkernel.conf -/etc/doas.conf +# core /etc/fstab -/etc/bluetooth/audio.conf -/etc/profile.d/toolbox.sh /etc/hosts +/etc/resolvconf.conf +/etc/resolv.conf +/etc/doas* +/etc/profile.d/toolbox.sh +/etc/profile.d/xdg-cache-home.sh +/etc/bluetooth +# kernel stuff +/etc/buildkernel.conf +/etc/genkernel.conf /usr/src/linux/.config -/usr/src/linux-5.10.76-gentoo-r1/.config -/usr/src/linux-5.15.16-gentoo/.config +/usr/src/linux-5.10*/.config +/usr/src/linux-5.15*/.config /var/spool/cron/crontabs/robert + +# wifi +/etc/iwd +/var/lib/iwd #secret +/etc/netplug.d + +# services +/etc/conf.d +/etc/runlevels + +/etc/buildkit +/etc/cgroup +/etc/default +/etc/env.d/99bemenu.in +/etc/env.d/99bemenu.out + +#/etc/greetd +#/etc/plymouth -- cgit 1.4.1