From 2c3514fe15c9ba2063681cad2235abd153caf503 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Sun, 14 Apr 2024 18:22:44 +0100 Subject: [PATCH 1/4] headless: remove ssh key from unpriv user --- nixos/modules/headless/docker.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/nixos/modules/headless/docker.nix b/nixos/modules/headless/docker.nix index d781ee57..a705d2ba 100755 --- a/nixos/modules/headless/docker.nix +++ b/nixos/modules/headless/docker.nix @@ -60,8 +60,6 @@ # Define the unpriv user for docker users.users.unpriv = { isNormalUser = true; - - openssh.authorizedKeys.keys = config.ahayzen.publicKeys.group.user.developers; }; virtualisation.docker = { From b2407edb7b021dac348ca940e1d4f641fabcb8e5 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Sun, 21 Apr 2024 16:07:31 +0100 Subject: [PATCH 2/4] docker: use fixed high id for unpriv user This should keep us consistent for backup purposes etc. --- nixos/modules/headless/docker.nix | 38 ++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/nixos/modules/headless/docker.nix b/nixos/modules/headless/docker.nix index a705d2ba..cc12306e 100755 --- a/nixos/modules/headless/docker.nix +++ b/nixos/modules/headless/docker.nix @@ -58,8 +58,40 @@ }; # Define the unpriv user for docker - users.users.unpriv = { - isNormalUser = true; + # + # Set this to a high id so that we remain stable + users = { + groups.unpriv = { + gid = 2000; + }; + users.unpriv = { + isNormalUser = true; + group = "unpriv"; + uid = 2000; + + # Map the root sub id to the same as the user (as it is unpriviledged) + # then map the remaining uids high + subGidRanges = [ + { + count = 1; + startGid = 2000; + } + { + count = 65535; + startGid = 200001; + } + ]; + subUidRanges = [ + { + count = 1; + startUid = 2000; + } + { + count = 65535; + startUid = 200001; + } + ]; + }; }; virtualisation.docker = { @@ -73,7 +105,7 @@ daemon.settings = { dns = [ "9.9.9.9" ]; no-new-privileges = true; - userns-remap = "unpriv:users"; + userns-remap = "unpriv:unpriv"; }; # rootless is too problematic as it requires services to run as user services From 1fdd01863a94f3346c91f7802463cf75dd146d34 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Sun, 14 Apr 2024 12:29:49 +0100 Subject: [PATCH 3/4] scripts: add a backup and restore script --- .github/workflows/check.yml | 12 +- scripts/backup.sh | 55 ++++++++ scripts/restore.sh | 61 +++++++++ tests/files/ssh_config | 6 + tests/files/test_ssh_id_ed25519 | 7 ++ tests/files/test_ssh_id_ed25519.license | 3 + tests/files/test_ssh_id_ed25519.pub | 1 + tests/files/test_ssh_id_ed25519.pub.license | 3 + .../wagtail-ahayzen/db/db.sqlite3 | Bin 0 -> 823296 bytes .../wagtail-ahayzen/db/db.sqlite3.license | 3 + tests/vps.nix | 119 ++++++++++++++++-- 11 files changed, 257 insertions(+), 13 deletions(-) create mode 100755 scripts/backup.sh create mode 100755 scripts/restore.sh create mode 100644 tests/files/ssh_config create mode 100644 tests/files/test_ssh_id_ed25519 create mode 100644 tests/files/test_ssh_id_ed25519.license create mode 100644 tests/files/test_ssh_id_ed25519.pub create mode 100644 tests/files/test_ssh_id_ed25519.pub.license create mode 100644 tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3 create mode 100644 tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3.license diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ea5986b7..99cd7019 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,9 +36,19 @@ jobs: - name: git diff run: git diff --exit-code + shellcheck: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + # Shellcheck should already be installed on github runners + - name: install shellcheck + run: sudo apt install --yes shellcheck + - name: shellcheck + run: shellcheck scripts/*.sh + nix-flake-check: # Run after pre checks - needs: [license-check, flake-checker, nix-fmt] + needs: [license-check, flake-checker, nix-fmt, shellcheck] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 00000000..33843295 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: Andrew Hayzen +# +# SPDX-License-Identifier: MPL-2.0 + +set -e + +# +# backup +# + +# Check that rsync exists +if [ ! -x "$(command -v rsync)" ]; then + echo "rsync command not found, cannot backup" + exit 1 +fi +RSYNC_ARGS=(--archive --human-readable --partial --progress --rsync-path="sudo rsync") + +HEADLESS_SYSTEM=false +USER_HOST=$2 + +# Check that the machine name is known +case $1 in + vps) + HEADLESS_SYSTEM=true + ;; + *) + echo "Unknown machine name" + exit 1 + ;; +esac + +# Check that the target folder exists +USER_DEST=$3 +if [ ! -d "$USER_DEST" ]; then + echo "Failed to find backup target" + exit 1 +fi +BACKUP_DEST="$USER_DEST" + +# This is a normal headless system +if [ $HEADLESS_SYSTEM ]; then + export DOCKER_COMPOSE_RUNNER_DEST="$BACKUP_DEST/docker-compose-runner/" + mkdir -p "$DOCKER_COMPOSE_RUNNER_DEST" + + # Backup all of the docker data + "$(command -v rsync)" "${RSYNC_ARGS[@]}" "$USER_HOST:/var/lib/docker-compose-runner/" "$DOCKER_COMPOSE_RUNNER_DEST" +fi + +# Ensure the filesystem is synced +sync + +echo "Backup complete!" +date diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 00000000..5d2f165e --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: Andrew Hayzen +# +# SPDX-License-Identifier: MPL-2.0 + +set -e + +# +# restore +# + +# Check that rsync exists +if [ ! -x "$(command -v rsync)" ]; then + echo "rsync command not found, cannot restore" + exit 1 +fi +RSYNC_ARGS=(--archive --human-readable --partial --progress --rsync-path="sudo rsync") + +HEADLESS_SYSTEM=false +USER_HOST=$2 + +# Check that the machine name is known +case $1 in + vps) + HEADLESS_SYSTEM=true + ;; + *) + echo "Unknown machine name" + exit 1 + ;; +esac + +# Check that the source folder exists +USER_SRC=$3 +if [ ! -d "$USER_SRC" ]; then + echo "Failed to find restore source" + exit 1 +fi +RESTORE_SRC="$USER_SRC" + +# This is a normal headless system +if [ $HEADLESS_SYSTEM ]; then + export DOCKER_COMPOSE_RUNNER_SRC="$RESTORE_SRC/docker-compose-runner/" + if [ ! -d "$DOCKER_COMPOSE_RUNNER_SRC" ]; then + echo "Failed to find docker-compose-runner data to restore" + exit 1 + fi + + # Stop services as we are about to mutate data + ssh "$USER_HOST" sudo systemctl stop docker-compose-runner.service + + # Restore all of the docker data + "$(command -v rsync)" "${RSYNC_ARGS[@]}" "$DOCKER_COMPOSE_RUNNER_SRC" "$USER_HOST:/var/lib/docker-compose-runner/" + + # Restart services + ssh "$USER_HOST" sudo systemctl start docker-compose-runner.service +fi + +echo "Restore complete!" +date diff --git a/tests/files/ssh_config b/tests/files/ssh_config new file mode 100644 index 00000000..364688c9 --- /dev/null +++ b/tests/files/ssh_config @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: Andrew Hayzen +# +# SPDX-License-Identifier: MPL-2.0 + +Host vps + IdentityFile /etc/ssh/test_ssh_id_ed25519 diff --git a/tests/files/test_ssh_id_ed25519 b/tests/files/test_ssh_id_ed25519 new file mode 100644 index 00000000..d0ed7f9e --- /dev/null +++ b/tests/files/test_ssh_id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBwClEXAWm/c9/42a0VdvVowOxAkdiJUFyeqUwkNP5ioQAAAIj3ZErF92RK +xQAAAAtzc2gtZWQyNTUxOQAAACBwClEXAWm/c9/42a0VdvVowOxAkdiJUFyeqUwkNP5ioQ +AAAEAwbllncNKWZCOFyHkejkA4GZNBl9O6IKQf3pKpMj22SHAKURcBab9z3/jZrRV29WjA +7ECR2IlQXJ6pTCQ0/mKhAAAABHRlc3QB +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/files/test_ssh_id_ed25519.license b/tests/files/test_ssh_id_ed25519.license new file mode 100644 index 00000000..7a058e16 --- /dev/null +++ b/tests/files/test_ssh_id_ed25519.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Andrew Hayzen +# +# SPDX-License-Identifier: CC0-1.0 diff --git a/tests/files/test_ssh_id_ed25519.pub b/tests/files/test_ssh_id_ed25519.pub new file mode 100644 index 00000000..61f2903c --- /dev/null +++ b/tests/files/test_ssh_id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHAKURcBab9z3/jZrRV29WjA7ECR2IlQXJ6pTCQ0/mKh test diff --git a/tests/files/test_ssh_id_ed25519.pub.license b/tests/files/test_ssh_id_ed25519.pub.license new file mode 100644 index 00000000..7a058e16 --- /dev/null +++ b/tests/files/test_ssh_id_ed25519.pub.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Andrew Hayzen +# +# SPDX-License-Identifier: CC0-1.0 diff --git a/tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3 b/tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..23f580ab381b137079010eb7326b6e36507e82ee GIT binary patch literal 823296 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 M59alE3`TwbA5_)#c>n+a literal 0 HcmV?d00001 diff --git a/tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3.license b/tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3.license new file mode 100644 index 00000000..7a058e16 --- /dev/null +++ b/tests/fixtures/vps/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Andrew Hayzen +# +# SPDX-License-Identifier: CC0-1.0 diff --git a/tests/vps.nix b/tests/vps.nix index b17fcf71..38a56db6 100644 --- a/tests/vps.nix +++ b/tests/vps.nix @@ -5,7 +5,7 @@ (import ./lib.nix) { name = "vps test"; nodes = { - machine = { self, pkgs, ... }: { + vps = { self, pkgs, ... }: { imports = [ self.nixosModules.headlessSystem @@ -21,26 +21,121 @@ networking.hosts = { "127.0.0.1" = [ "ahayzen.com" ]; }; + + # Allow test ssh authentication + users.users.headless.openssh.authorizedKeys.keyFiles = [ + ./files/test_ssh_id_ed25519.pub + ]; + }; + + backup = { self, pkgs, ... }: { + environment = { + etc = { + # Map backup and restore scripts + "ahayzen.com/backup.sh".source = ../scripts/backup.sh; + "ahayzen.com/restore.sh".source = ../scripts/restore.sh; + + # Map restore fixtures + "ahayzen.com/restore/fixtures".source = ./fixtures/vps; + + # Map the test SSH key for backups + "ssh/test_ssh_id_ed25519" = { + mode = "0400"; + source = ./files/test_ssh_id_ed25519; + }; + "ssh/test_ssh_id_ed25519.pub".source = ./files/test_ssh_id_ed25519.pub; + }; + + # Extra packages for the test + systemPackages = with pkgs; [ + python3 + rsync + ]; + }; + + services.openssh.enable = true; + + # Setup IdentityFile for vps + programs.ssh.extraConfig = builtins.readFile ./files/ssh_config; }; }; testScript = '' start_all() - # Wait for docker runner - machine.wait_for_unit("docker-compose-runner", timeout=90) + wait_for_wagtail_cmd = 'journalctl --boot --no-pager --quiet --unit docker.service --grep "\[INFO\] Listening at: http:\/\/0\.0\.0\.0:8080"' + + # + # Test that the VPS boots and shows wagtail admin + # + + with subtest("Ensure docker starts and wagtail admin works"): + # Wait for docker runner + vps.wait_for_unit("docker-compose-runner", timeout=90) + + # Wait for caddy to start + vps.wait_for_open_port(80, timeout=60) + + # Wait for wagtail to start + vps.wait_until_succeeds(wait_for_wagtail_cmd, timeout=60) + + # Test that admin page exists + output = vps.succeed("curl --silent ahayzen.com:80/admin/login/?next=/admin/") + assert "Sign in" in output, f"'{output}' does not contain 'Sign in'" + + # Test that wagtail port is not open externally + vps.fail("curl --silent ahayzen.com:8080") + + # + # Test that we can backup and restore the VPS + # + + with subtest("Access hostkey"): + vps.wait_for_open_port(22, timeout=30) + # Ensure we allow the host key + backup.succeed("ssh -vvv -o StrictHostKeyChecking=accept-new headless@vps exit") + + with subtest("Attempt to run a backup"): + backup.succeed("mkdir -p /tmp/backup-root") + + # Run the backup + backup.succeed("/etc/ahayzen.com/backup.sh vps headless@vps /tmp/backup-root") + + # Check volumes are appearing + backup.succeed("test -d /tmp/backup-root/docker-compose-runner/caddy/persistent") + backup.succeed("test -d /tmp/backup-root/docker-compose-runner/caddy/config") + backup.succeed("test -d /tmp/backup-root/docker-compose-runner/wagtail-ahayzen/db") + backup.succeed("test -d /tmp/backup-root/docker-compose-runner/wagtail-ahayzen/media") + backup.succeed("test -d /tmp/backup-root/docker-compose-runner/wagtail-ahayzen/static") + + # Check that known files exist + backup.succeed("test -e /tmp/backup-root/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3") + + with subtest("Attempt to run a restore"): + # Check the home does not contain restore key + output = vps.succeed("curl --silent ahayzen.com:80/") + assert "Restore Unit Test" not in output, f"'{output}' does contain 'Restore Unit Test'" + + # Copy fixtures to a /tmp folder so that we can ix permissions + # as environment.etc..user only affects files + backup.succeed("mkdir -p /tmp/restore-root") + backup.succeed("cp -R /etc/ahayzen.com/restore/fixtures/* /tmp/restore-root/") + backup.succeed("chown -R 2000:2000 /tmp/restore-root/") - # Wait for caddy to start - machine.wait_for_open_port(80, timeout=60) + # Check files exist + backup.succeed("test -d /tmp/restore-root/test-page/docker-compose-runner/wagtail-ahayzen/db") + backup.succeed("test -e /tmp/restore-root/test-page/docker-compose-runner/wagtail-ahayzen/db/db.sqlite3") - # Wait for wagtail to start - machine.wait_until_succeeds('journalctl --boot --no-pager --quiet --unit docker.service --grep "\[INFO\] Listening at: http:\/\/0\.0\.0\.0:8080"', timeout=60) + # Run the restore + backup.succeed("/etc/ahayzen.com/restore.sh vps headless@vps /tmp/restore-root/test-page") - # Test that admin page exists - output = machine.succeed("curl --silent ahayzen.com:80/admin/login/?next=/admin/") - assert "Sign in" in output, f"'{output}' does not contain 'Sign in'" + # Wait for services to restart and second occurance of Listening at + vps.wait_for_unit("docker-compose-runner", timeout=90) + vps.wait_for_open_port(80, timeout=60) + vps.wait_until_succeeds(wait_for_wagtail_cmd + " | wc -l | awk '{if ($1 > 1) {exit 0} else {exit 1}}'", timeout=60) - # Test that wagtail port is not open externally - machine.fail("curl --silent ahayzen.com:8080") + # Check the home does contain restore key + output = vps.succeed("curl --silent ahayzen.com:80/") + assert "Restore Unit Test" in output, f"'{output}' does not contain 'Restore Unit Test'" ''; } From 48894654304a0c32501a7eead16229f1b654a7e7 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Sun, 21 Apr 2024 19:39:33 +0100 Subject: [PATCH 4/4] docs: add more info around how to test via VMs --- docs/commands/testing.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/commands/testing.md b/docs/commands/testing.md index ab99b0e2..104f2439 100644 --- a/docs/commands/testing.md +++ b/docs/commands/testing.md @@ -4,8 +4,27 @@ SPDX-FileCopyrightText: Andrew Hayzen SPDX-License-Identifier: MPL-2.0 --> +# Test with flake check + +We need the sandbox disabled as we need network access + +```console +$ nix flake --option sandbox false check -L --show-trace +``` + # Test in a VM +Ensure that you add the following snippet to the configuration of the machine you want to test in a VM. + +```nix +{ + ahayzen.testing = true; +} +``` + +> Note that if you are testing http update any `Caddyfile.vm` to use `http://localhost` +> rather than `http://mydomain.com` to access locally. + ## `nixos-build` ```console @@ -31,11 +50,3 @@ $ result/bin/run--vm ```console $ ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no admin@localhost -p 2221 ``` - -## Flake check - -We need the sandbox disabled as we need network access - -```console -$ nix flake --option sandbox false check -L --show-trace -```