From d428faa9a35aceff6ec7204ae9ea5390391c9a26 Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra <kiara@procolix.eu> Date: Wed, 26 Mar 2025 09:57:02 +0100 Subject: [PATCH] properly pass repo dir for prod, be it with hard-coded TF init --- flake.nix | 5 +- launch/.gitignore | 2 +- launch/.terraform/modules/mastodon.deploy | 1 + launch/.terraform/modules/modules.json | 1 + .../modules/peertube.deploy/CONTRIBUTING.md | 23 + .../modules/peertube.deploy/LICENSE | 21 + .../modules/peertube.deploy/README.md | 123 +++ .../modules/peertube.deploy/docs/INDEX.md | 11 + .../modules/peertube.deploy/docs/SUMMARY.md | 26 + .../modules/peertube.deploy/docs/book.toml | 6 + .../modules/peertube.deploy/docs/cli.md | 63 ++ .../peertube.deploy/docs/flake-module.nix | 21 + .../modules/peertube.deploy/docs/howtos.md | 1 + .../peertube.deploy/docs/howtos/INDEX.md | 29 + .../docs/howtos/custom-kexec.md | 40 + .../docs/howtos/disko-modes.md | 19 + .../docs/howtos/extra-files.md | 114 +++ .../peertube.deploy/docs/howtos/ipv6.md | 23 + .../docs/howtos/limited-ram.md | 29 + .../peertube.deploy/docs/howtos/nix-path.md | 57 ++ .../peertube.deploy/docs/howtos/no-os.md | 71 ++ .../peertube.deploy/docs/howtos/secrets.md | 76 ++ .../peertube.deploy/docs/howtos/terraform.md | 23 + .../docs/howtos/use-without-flakes.md | 82 ++ .../modules/peertube.deploy/docs/logo.png | Bin 0 -> 29405 bytes .../modules/peertube.deploy/docs/logo.svg | 133 +++ .../peertube.deploy/docs/quickstart.md | 334 +++++++ .../modules/peertube.deploy/docs/reference.md | 137 +++ .../peertube.deploy/docs/requirements.md | 39 + .../modules/peertube.deploy/flake.lock | 132 +++ .../modules/peertube.deploy/flake.nix | 55 ++ .../peertube.deploy/scripts/create-release.sh | 38 + .../modules/peertube.deploy/src/default.nix | 63 ++ .../peertube.deploy/src/flake-module.nix | 11 + .../modules/peertube.deploy/src/get-facts.sh | 23 + .../peertube.deploy/src/nixos-anywhere.sh | 880 ++++++++++++++++++ .../peertube.deploy/terraform/README.md | 21 + .../peertube.deploy/terraform/all-in-one.md | 235 +++++ .../terraform/all-in-one/main.tf | 64 ++ .../terraform/all-in-one/variables.tf | 151 +++ .../peertube.deploy/terraform/install.md | 91 ++ .../peertube.deploy/terraform/install/main.tf | 35 + .../terraform/install/providers.tf | 5 + .../terraform/install/run-nixos-anywhere.sh | 92 ++ .../terraform/install/variables.tf | 123 +++ .../peertube.deploy/terraform/nix-build.md | 47 + .../terraform/nix-build/main.tf | 17 + .../terraform/nix-build/nix-build.sh | 57 ++ .../terraform/nix-build/variables.tf | 22 + .../terraform/nixos-rebuild.md | 66 ++ .../terraform/nixos-rebuild/deploy.sh | 68 ++ .../terraform/nixos-rebuild/main.tf | 11 + .../terraform/nixos-rebuild/variables.tf | 39 + .../peertube.deploy/terraform/update-docs.sh | 14 + .../peertube.deploy/tests/flake-module.nix | 33 + .../tests/from-nixos-build-on-remote.nix | 56 ++ .../tests/from-nixos-generate-config.nix | 71 ++ .../tests/from-nixos-separated-phases.nix | 52 ++ .../tests/from-nixos-with-sudo.nix | 40 + .../peertube.deploy/tests/from-nixos.nix | 76 ++ .../peertube.deploy/tests/lib/test-base.nix | 20 + .../tests/modules/installer.nix | 22 + .../tests/modules/ssh-keys/ssh | 7 + .../tests/modules/ssh-keys/ssh.pub | 1 + .../tests/modules/system-to-install.nix | 47 + .../peertube.deploy/treefmt/flake-module.nix | 23 + .../modules/pixelfed.deploy/CONTRIBUTING.md | 23 + .../modules/pixelfed.deploy/LICENSE | 21 + .../modules/pixelfed.deploy/README.md | 123 +++ .../modules/pixelfed.deploy/docs/INDEX.md | 11 + .../modules/pixelfed.deploy/docs/SUMMARY.md | 26 + .../modules/pixelfed.deploy/docs/book.toml | 6 + .../modules/pixelfed.deploy/docs/cli.md | 63 ++ .../pixelfed.deploy/docs/flake-module.nix | 21 + .../modules/pixelfed.deploy/docs/howtos.md | 1 + .../pixelfed.deploy/docs/howtos/INDEX.md | 29 + .../docs/howtos/custom-kexec.md | 40 + .../docs/howtos/disko-modes.md | 19 + .../docs/howtos/extra-files.md | 114 +++ .../pixelfed.deploy/docs/howtos/ipv6.md | 23 + .../docs/howtos/limited-ram.md | 29 + .../pixelfed.deploy/docs/howtos/nix-path.md | 57 ++ .../pixelfed.deploy/docs/howtos/no-os.md | 71 ++ .../pixelfed.deploy/docs/howtos/secrets.md | 76 ++ .../pixelfed.deploy/docs/howtos/terraform.md | 23 + .../docs/howtos/use-without-flakes.md | 82 ++ .../modules/pixelfed.deploy/docs/logo.png | Bin 0 -> 29405 bytes .../modules/pixelfed.deploy/docs/logo.svg | 133 +++ .../pixelfed.deploy/docs/quickstart.md | 334 +++++++ .../modules/pixelfed.deploy/docs/reference.md | 137 +++ .../pixelfed.deploy/docs/requirements.md | 39 + .../modules/pixelfed.deploy/flake.lock | 132 +++ .../modules/pixelfed.deploy/flake.nix | 55 ++ .../pixelfed.deploy/scripts/create-release.sh | 38 + .../modules/pixelfed.deploy/src/default.nix | 63 ++ .../pixelfed.deploy/src/flake-module.nix | 11 + .../modules/pixelfed.deploy/src/get-facts.sh | 23 + .../pixelfed.deploy/src/nixos-anywhere.sh | 880 ++++++++++++++++++ .../pixelfed.deploy/terraform/README.md | 21 + .../pixelfed.deploy/terraform/all-in-one.md | 235 +++++ .../terraform/all-in-one/main.tf | 64 ++ .../terraform/all-in-one/variables.tf | 151 +++ .../pixelfed.deploy/terraform/install.md | 91 ++ .../pixelfed.deploy/terraform/install/main.tf | 35 + .../terraform/install/providers.tf | 5 + .../terraform/install/run-nixos-anywhere.sh | 92 ++ .../terraform/install/variables.tf | 123 +++ .../pixelfed.deploy/terraform/nix-build.md | 47 + .../terraform/nix-build/main.tf | 17 + .../terraform/nix-build/nix-build.sh | 57 ++ .../terraform/nix-build/variables.tf | 22 + .../terraform/nixos-rebuild.md | 66 ++ .../terraform/nixos-rebuild/deploy.sh | 68 ++ .../terraform/nixos-rebuild/main.tf | 11 + .../terraform/nixos-rebuild/variables.tf | 39 + .../pixelfed.deploy/terraform/update-docs.sh | 14 + .../pixelfed.deploy/tests/flake-module.nix | 33 + .../tests/from-nixos-build-on-remote.nix | 56 ++ .../tests/from-nixos-generate-config.nix | 71 ++ .../tests/from-nixos-separated-phases.nix | 52 ++ .../tests/from-nixos-with-sudo.nix | 40 + .../pixelfed.deploy/tests/from-nixos.nix | 76 ++ .../pixelfed.deploy/tests/lib/test-base.nix | 20 + .../tests/modules/installer.nix | 22 + .../tests/modules/ssh-keys/ssh | 7 + .../tests/modules/ssh-keys/ssh.pub | 1 + .../tests/modules/system-to-install.nix | 47 + .../pixelfed.deploy/treefmt/flake-module.nix | 23 + launch/.terraform/plugin_path | 3 + .../hashicorp/external/2.3.4/linux_amd64 | 1 + .../hashicorp/null/3.2.3/linux_amd64 | 1 + launch/tf-env.nix | 36 + panel/default.nix | 17 +- panel/env.nix | 5 - panel/nix/configuration.nix | 1 + panel/nix/package.nix | 3 +- panel/src/panel/settings.py | 17 +- 137 files changed, 8682 insertions(+), 29 deletions(-) create mode 120000 launch/.terraform/modules/mastodon.deploy create mode 100644 launch/.terraform/modules/modules.json create mode 100644 launch/.terraform/modules/peertube.deploy/CONTRIBUTING.md create mode 100644 launch/.terraform/modules/peertube.deploy/LICENSE create mode 100644 launch/.terraform/modules/peertube.deploy/README.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/INDEX.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/SUMMARY.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/book.toml create mode 100644 launch/.terraform/modules/peertube.deploy/docs/cli.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/flake-module.nix create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/INDEX.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/custom-kexec.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/disko-modes.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/extra-files.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/ipv6.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/limited-ram.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/nix-path.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/no-os.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/secrets.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/terraform.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/howtos/use-without-flakes.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/logo.png create mode 100644 launch/.terraform/modules/peertube.deploy/docs/logo.svg create mode 100644 launch/.terraform/modules/peertube.deploy/docs/quickstart.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/reference.md create mode 100644 launch/.terraform/modules/peertube.deploy/docs/requirements.md create mode 100644 launch/.terraform/modules/peertube.deploy/flake.lock create mode 100644 launch/.terraform/modules/peertube.deploy/flake.nix create mode 100755 launch/.terraform/modules/peertube.deploy/scripts/create-release.sh create mode 100644 launch/.terraform/modules/peertube.deploy/src/default.nix create mode 100644 launch/.terraform/modules/peertube.deploy/src/flake-module.nix create mode 100755 launch/.terraform/modules/peertube.deploy/src/get-facts.sh create mode 100755 launch/.terraform/modules/peertube.deploy/src/nixos-anywhere.sh create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/README.md create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/all-in-one.md create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/all-in-one/main.tf create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/all-in-one/variables.tf create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/install.md create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/install/main.tf create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/install/providers.tf create mode 100755 launch/.terraform/modules/peertube.deploy/terraform/install/run-nixos-anywhere.sh create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/install/variables.tf create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/nix-build.md create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/nix-build/main.tf create mode 100755 launch/.terraform/modules/peertube.deploy/terraform/nix-build/nix-build.sh create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/nix-build/variables.tf create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild.md create mode 100755 launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/deploy.sh create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/main.tf create mode 100644 launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/variables.tf create mode 100755 launch/.terraform/modules/peertube.deploy/terraform/update-docs.sh create mode 100644 launch/.terraform/modules/peertube.deploy/tests/flake-module.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/from-nixos-build-on-remote.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/from-nixos-generate-config.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/from-nixos-separated-phases.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/from-nixos-with-sudo.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/from-nixos.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/lib/test-base.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/modules/installer.nix create mode 100644 launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh create mode 100644 launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh.pub create mode 100644 launch/.terraform/modules/peertube.deploy/tests/modules/system-to-install.nix create mode 100644 launch/.terraform/modules/peertube.deploy/treefmt/flake-module.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/CONTRIBUTING.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/LICENSE create mode 100644 launch/.terraform/modules/pixelfed.deploy/README.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/INDEX.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/SUMMARY.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/book.toml create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/cli.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/flake-module.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/INDEX.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/custom-kexec.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/disko-modes.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/extra-files.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/ipv6.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/limited-ram.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/nix-path.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/no-os.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/secrets.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/terraform.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/howtos/use-without-flakes.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/logo.png create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/logo.svg create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/quickstart.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/reference.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/docs/requirements.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/flake.lock create mode 100644 launch/.terraform/modules/pixelfed.deploy/flake.nix create mode 100755 launch/.terraform/modules/pixelfed.deploy/scripts/create-release.sh create mode 100644 launch/.terraform/modules/pixelfed.deploy/src/default.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/src/flake-module.nix create mode 100755 launch/.terraform/modules/pixelfed.deploy/src/get-facts.sh create mode 100755 launch/.terraform/modules/pixelfed.deploy/src/nixos-anywhere.sh create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/README.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/main.tf create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/variables.tf create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/install.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/install/main.tf create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/install/providers.tf create mode 100755 launch/.terraform/modules/pixelfed.deploy/terraform/install/run-nixos-anywhere.sh create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/install/variables.tf create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/nix-build.md create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/main.tf create mode 100755 launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/nix-build.sh create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/variables.tf create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild.md create mode 100755 launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/deploy.sh create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/main.tf create mode 100644 launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/variables.tf create mode 100755 launch/.terraform/modules/pixelfed.deploy/terraform/update-docs.sh create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/flake-module.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-build-on-remote.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-generate-config.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-separated-phases.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-with-sudo.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/from-nixos.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/lib/test-base.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/modules/installer.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh.pub create mode 100644 launch/.terraform/modules/pixelfed.deploy/tests/modules/system-to-install.nix create mode 100644 launch/.terraform/modules/pixelfed.deploy/treefmt/flake-module.nix create mode 100644 launch/.terraform/plugin_path create mode 120000 launch/.terraform/providers/registry.opentofu.org/hashicorp/external/2.3.4/linux_amd64 create mode 120000 launch/.terraform/providers/registry.opentofu.org/hashicorp/null/3.2.3/linux_amd64 create mode 100644 launch/tf-env.nix diff --git a/flake.nix b/flake.nix index 9e0a719..dba9e55 100644 --- a/flake.nix +++ b/flake.nix @@ -43,7 +43,10 @@ pre-commit.settings.hooks = let ## Add a directory here if pre-commit hooks shouldn't apply to it. - optout = [ "npins" ]; + optout = [ + "npins" + "launch/.terraform" + ]; excludes = map (dir: "^${dir}/") optout; addExcludes = lib.mapAttrs (_: c: c // { inherit excludes; }); in diff --git a/launch/.gitignore b/launch/.gitignore index 0f8ba9b..c0b634d 100644 --- a/launch/.gitignore +++ b/launch/.gitignore @@ -1,3 +1,3 @@ -.terraform/ +# .terraform/ .terraform.tfstate.lock.info terraform.tfstate* diff --git a/launch/.terraform/modules/mastodon.deploy b/launch/.terraform/modules/mastodon.deploy new file mode 120000 index 0000000..f997bef --- /dev/null +++ b/launch/.terraform/modules/mastodon.deploy @@ -0,0 +1 @@ +/nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source \ No newline at end of file diff --git a/launch/.terraform/modules/modules.json b/launch/.terraform/modules/modules.json new file mode 100644 index 0000000..5835489 --- /dev/null +++ b/launch/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"mastodon","Source":"./vm","Dir":"vm"},{"Key":"mastodon.deploy","Source":"file:///nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source//terraform/all-in-one","Dir":".terraform/modules/mastodon.deploy/terraform/all-in-one"},{"Key":"mastodon.deploy.install","Source":"../install","Dir":"/nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source/terraform/install"},{"Key":"mastodon.deploy.nixos-rebuild","Source":"../nixos-rebuild","Dir":"/nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source/terraform/nixos-rebuild"},{"Key":"mastodon.deploy.partitioner-build","Source":"../nix-build","Dir":"/nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source/terraform/nix-build"},{"Key":"mastodon.deploy.system-build","Source":"../nix-build","Dir":"/nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source/terraform/nix-build"},{"Key":"peertube","Source":"./vm","Dir":"vm"},{"Key":"peertube.deploy","Source":"file:///nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source//terraform/all-in-one","Dir":".terraform/modules/peertube.deploy/terraform/all-in-one"},{"Key":"peertube.deploy.install","Source":"../install","Dir":".terraform/modules/peertube.deploy/terraform/install"},{"Key":"peertube.deploy.nixos-rebuild","Source":"../nixos-rebuild","Dir":".terraform/modules/peertube.deploy/terraform/nixos-rebuild"},{"Key":"peertube.deploy.partitioner-build","Source":"../nix-build","Dir":".terraform/modules/peertube.deploy/terraform/nix-build"},{"Key":"peertube.deploy.system-build","Source":"../nix-build","Dir":".terraform/modules/peertube.deploy/terraform/nix-build"},{"Key":"pixelfed","Source":"./vm","Dir":"vm"},{"Key":"pixelfed.deploy","Source":"file:///nix/store/y5ripxky2azz90fzpsj6383f1vljrxkg-source//terraform/all-in-one","Dir":".terraform/modules/pixelfed.deploy/terraform/all-in-one"},{"Key":"pixelfed.deploy.install","Source":"../install","Dir":".terraform/modules/pixelfed.deploy/terraform/install"},{"Key":"pixelfed.deploy.nixos-rebuild","Source":"../nixos-rebuild","Dir":".terraform/modules/pixelfed.deploy/terraform/nixos-rebuild"},{"Key":"pixelfed.deploy.partitioner-build","Source":"../nix-build","Dir":".terraform/modules/pixelfed.deploy/terraform/nix-build"},{"Key":"pixelfed.deploy.system-build","Source":"../nix-build","Dir":".terraform/modules/pixelfed.deploy/terraform/nix-build"}]} \ No newline at end of file diff --git a/launch/.terraform/modules/peertube.deploy/CONTRIBUTING.md b/launch/.terraform/modules/peertube.deploy/CONTRIBUTING.md new file mode 100644 index 0000000..3aeefa7 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/CONTRIBUTING.md @@ -0,0 +1,23 @@ +To run `nixos-anywhere` from the repo: + +```console +nix run . -- --help +``` + +To format the code: + +```console +nix fmt +``` + +To run all tests: + +```console +nix flake check -vL +``` + +To run an individual test: + +``` +nix build .#checks.x86_64-linux.from-nixos -vL +``` diff --git a/launch/.terraform/modules/peertube.deploy/LICENSE b/launch/.terraform/modules/peertube.deploy/LICENSE new file mode 100644 index 0000000..1996195 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Numtide + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/launch/.terraform/modules/peertube.deploy/README.md b/launch/.terraform/modules/peertube.deploy/README.md new file mode 100644 index 0000000..cdf28cc --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/README.md @@ -0,0 +1,123 @@ +# nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" width="150" height="150"> + +[Documentation Index](docs/INDEX.md) + +## README + +Setting up a new machine is time-consuming, and becomes complicated when it +needs to be done remotely. If you're installing NixOS, the **nixos-anywhere** +tool allows you to pre-configure the whole process including: + +- Disk partitioning and formatting +- Configuring and installing NixOS +- Installing additional files and software + +You can then initiate an unattended installation with a single CLI command. +Since **nixos-anywhere** can access the new machine using SSH, it's ideal for +remote installations. + +Once you have initiated the command, there is no need to 'babysit' the +installation. It all happens automatically. + +You can use the stored configuration to repeat the same installation if you need +to. + +## Overview + +If you have machines on a mix of platforms, you'll need a common installation +solution that works anywhere. **nixos-anywhere** is ideal in this situation. + +**nixos-anywhere** can be used equally well for cloud servers, bare metal +servers such as Hetzner, and local servers accessible via a LAN. You can create +standard configurations, and use the same configuration to create identical +servers anywhere. + +You first create Nix configurations to specify partitioning, formatting and +NixOS configurations. Further options can be controlled by a flake and by +run-time switches. + +Once the configuration has been created, a single command will: + +- Connect to the remote server via SSH +- Detect whether a NixOS installer is present; if not, it will use the Linux + `kexec` tool to boot into a Nixos installer. +- Use the [disko](https://github.com/nix-community/disko) tool to partition and + format the hard drive +- Install NixOS +- Optionally install any Nix packages and other software required. +- Optionally copy additional files to the new machine + +It's also possible to use **nixos-anywhere** to simplify the installation on a +machine that has no current operating system, first booting from a NixOS +installer image. This feature is described in the +[how-to guide](./docs/howtos/no-os.md#installing-on-a-machine-with-no-operating-system). +It's useful because you can pre-configure your required software and +preferences, and build the new machine with a single command. + +**Important Note:** Never use a production server as the target. It will be +completely overwritten and all data lost. This tool should only be used for +commissioning a new computer or repurposing an old machine once all important +data has been migrated. + +## Prerequisites + +- Source Machine: + + - Can be any machine with Nix installed, e.g. a NixOS machine. + +- Target Machine: + + - Unless you're using the option to boot from a NixOS installer image, or + providing your own `kexec` image, it must be running x86-64 Linux with kexec + support. Most `x86_64` Linux systems do have kexec support. By providing + your own [image](./docs/howtos/custom-kexec.md#using-your-own-kexec-image) + you can also perform kexec for other architectures eg aarch64 + - The machine must be reachable over the public internet or local network. + Nixos-anywhere does not support wifi networks. If a VPN is needed, define a + custom installer via the --kexec flag which connects to your VPN. + - When `kexec` is used the target must have at least 1 GB of RAM, excluding + swap. + +## How to use nixos-anywhere + +The [Quickstart Guide](./docs/quickstart.md) gives more information on how to +run **nixos-anywhere** in its simplest form. For more specific instructions to +suit individual requirements, see the [How To Guide](./docs/howtos/INDEX.md). + +## Related Tools + +**nixos-anywhere** makes use of the +[disko](https://github.com/nix-community/disko) tool to handle the partitioning +and formatting of the disks. + +## Contact + +For questions, come join us in the +[nixos-anywhere](https://matrix.to/#/#nixos-anywhere:nixos.org) matrix room. + +## Licensing and Contribution details + +This software is provided free under the +[MIT License](https://opensource.org/licenses/MIT). + +--- + +This project is supported by [Numtide](https://numtide.com/). + + +We are a team of independent freelancers that love open source. We help our +customers make their project lifecycles more efficient by: + +- Providing and supporting useful tools such as this one +- Building and deploying infrastructure, and offering dedicated DevOps support +- Building their in-house Nix skills, and integrating Nix with their workflows +- Developing additional features and tools +- Carrying out custom research and development. + +[Contact us](https://numtide.com/contact) if you have a project in mind, or if +you need help with any of our supported tools, including this one. We'd love to +hear from you. diff --git a/launch/.terraform/modules/peertube.deploy/docs/INDEX.md b/launch/.terraform/modules/peertube.deploy/docs/INDEX.md new file mode 100644 index 0000000..67d4875 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/INDEX.md @@ -0,0 +1,11 @@ +# Table of Content: - nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="149"> + +- [README](../README.md) +- [Quickstart](./quickstart.md) +- [System Requirements](./requirements.md) +- [How to Guide](./howtos/INDEX.md) +- [Reference](./reference.md) diff --git a/launch/.terraform/modules/peertube.deploy/docs/SUMMARY.md b/launch/.terraform/modules/peertube.deploy/docs/SUMMARY.md new file mode 100644 index 0000000..3bfaea5 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/SUMMARY.md @@ -0,0 +1,26 @@ +# Summary: - nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="149"> + +The **nixos-anywhere** tool allows you to pre-configure the whole process of +installing NixOS, and run the install remotely with a single CLI command. + +Refer to the following documentation for more information. + +[System Requirements](./requirements.md): CPU and memory requirements + +[Quickstart](./quickstart.md): Instructions for a typical installation + +[How to Guide](./howtos/INDEX.md): Instructions for non-typical use cases + +- [Installing on a machine with no operating system](./howtos/no-os.md) +- [Using your own kexec image](./howtos/custom-kexec.md) +- [Secrets and full disk encryption](./howtos/secrets.md) +- [Use without flakes](./howtos/use-without-flakes.md) +- [Terraform](./howtos/terraform.md) +- [Nix-channels / `NIX_PATH`](./howtos/nix-path.md) +- [IPv6-only targets](./howtos/ipv6.md) + +[Reference](./reference.md): Reference Guide diff --git a/launch/.terraform/modules/peertube.deploy/docs/book.toml b/launch/.terraform/modules/peertube.deploy/docs/book.toml new file mode 100644 index 0000000..d053de4 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = [ ] +language = "en" +multilingual = false +src = "." +title = "nixos-anywhere - install NixOS everywhere" diff --git a/launch/.terraform/modules/peertube.deploy/docs/cli.md b/launch/.terraform/modules/peertube.deploy/docs/cli.md new file mode 100644 index 0000000..d257755 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/cli.md @@ -0,0 +1,63 @@ +# CLI + +``` +Usage: nixos-anywhere [options] [<ssh-host>] + +Options: + +* -f, --flake <flake_uri> + set the flake to install the system from. +* --target-host <ssh-host> + specified the SSH target host to deploy onto. +* -i <identity_file> + selects which SSH private key file to use. +* -p, --ssh-port <ssh_port> + set the ssh port to connect with +* --ssh-option <ssh_option> + set an ssh option +* -L, --print-build-logs + print full build logs +* --env-password + set a password used by ssh-copy-id, the password should be set by + the environment variable SSHPASS +* -s, --store-paths <disko-script> <nixos-system> + set the store paths to the disko-script and nixos-system directly + if this is given, flake is not needed +* --no-reboot + do not reboot after installation, allowing further customization of the target installation. +* --kexec <path> + use another kexec tarball to bootstrap NixOS +* --kexec-extra-flags + extra flags to add into the call to kexec, e.g. "--no-sync" +* --ssh-store-setting <key> <value> + ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded. +* --post-kexec-ssh-port <ssh_port> + after kexec is executed, use a custom ssh port to connect. Defaults to 22 +* --copy-host-keys + copy over existing /etc/ssh/ssh_host_* host keys to the installation +* --stop-after-disko + exit after disko formatting, you can then proceed to install manually or some other way +* --extra-files <path> + contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten + Copied files will be owned by root. See documentation for details. +* --disk-encryption-keys <remote_path> <local_path> + copy the contents of the file or pipe in local_path to remote_path in the installer environment, + after kexec but before installation. Can be repeated. +* --no-substitute-on-destination + disable passing --substitute-on-destination to nix-copy +* --debug + enable debug output +* --option <key> <value> + nix option to pass to every nix related command +* --from <store-uri> + URL of the source Nix store to copy the nixos and disko closure from +* --build-on-remote + build the closure on the remote machine instead of locally and copy-closuring it +* --vm-test + build the system and test the disk configuration inside a VM without installing it to the target. +* --build-on auto|remote|local + sets the build on settings to auto, remote or local. Default is auto. + auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build + local: will build on the local host + remote: will build on the remote host +``` diff --git a/launch/.terraform/modules/peertube.deploy/docs/flake-module.nix b/launch/.terraform/modules/peertube.deploy/docs/flake-module.nix new file mode 100644 index 0000000..2afe9d5 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/flake-module.nix @@ -0,0 +1,21 @@ +{ + perSystem = + { pkgs, lib, ... }: + { + packages.docs = + pkgs.runCommand "nixos-anywhere-docs" + { + passthru.serve = pkgs.writeShellScriptBin "serve" '' + set -euo pipefail + cd docs + workdir=$(${pkgs.coreutils}/bin/mktemp -d) + trap 'rm -rf "$workdir"' EXIT + ${pkgs.mdbook}/bin/mdbook serve --dest-dir "$workdir" + ''; + } + '' + cp -r ${lib.cleanSource ./.}/* . + ${pkgs.mdbook}/bin/mdbook build --dest-dir "$out" + ''; + }; +} diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos.md b/launch/.terraform/modules/peertube.deploy/docs/howtos.md new file mode 100644 index 0000000..bfa55ce --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos.md @@ -0,0 +1 @@ +# How to Guide diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/INDEX.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/INDEX.md new file mode 100644 index 0000000..2d35663 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/INDEX.md @@ -0,0 +1,29 @@ +# How To Guide: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="129"> + +[Documentation Index](./INDEX.md) + +## Contents + +[Installing on a machine with no operating system](./no-os.md) + +[Kexec on systems with limited RAM](./limited-ram.md) + +[Copying files to the new installation](./extra-files.md) + +[Using your own kexec image](./custom-kexec.md) + +[Repair installations without wiping data](./disko-modes.md) + +[Secrets and full disk encryption](./secrets.md) + +[Use without flakes](./use-without-flakes.md) + +[Terraform](./terraform.md) + +[Nix-channels / `NIX_PATH`](./nix-path.md) + +[IPv6-only targets](./ipv6.md) diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/custom-kexec.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/custom-kexec.md new file mode 100644 index 0000000..5935a5f --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/custom-kexec.md @@ -0,0 +1,40 @@ +# Using your own kexec image + +By default, `nixos-anywhere` downloads the kexec image from the +[NixOS images repository](https://github.com/nix-community/nixos-images#kexec-tarballs). + +However, you can provide your own `kexec` image file if you need to use a +different one. This is particularly useful for architectures other than `x86_64` +and `aarch64`, since they don't have a pre-build image. + +To do this, use the `--kexec` command line switch followed by the path to your +image file. The image will be uploaded prior to execution. + +Here's an example command that demonstrates how to use a custom kexec image with +`nixos-anywhere`: + +``` +nix run github:nix-community/nixos-anywhere -- \ + --kexec "$(nix build --print-out-paths github:nix-community/nixos-images#packages.aarch64-linux.kexec-installer-nixos-unstable-noninteractive)/nixos-kexec-installer-noninteractive-aarch64-linux.tar.gz" \ + --flake 'github:your-user/your-repo#your-system' \ + root@yourip +``` + +Make sure to replace `github:your-user/your-repo#your-system` with the +appropriate Flake URL representing your NixOS configuration. + +The example above assumes that your local machine can build for aarch64 in one +of the following ways: + +- Natively + +- Through a remote builder + +- By emulating the architecture with qemu using the following NixOS + configuration: + +```nix +{ + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; +} +``` diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/disko-modes.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/disko-modes.md new file mode 100644 index 0000000..501f93f --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/disko-modes.md @@ -0,0 +1,19 @@ +# Repair installations without wiping data + +By default, nixos-anywhere will reformat all configured disks before running the +installation. However it is also possible to mount the filesystems of an +existing installation and run `nixos-install`. This is useful to recover from a +misconfigured NixOS installation by first booting into a NixOS installer or +recovery system. + +To only mount existing filesystems, add `--disko-mode mount` to +`nixos-anywhere`: + +``` +nix run github:nix-community/nixos-anywhere -- --disko-mode mount --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +1. This will first boot into a nixos-installer +2. Mounts disks with disko +3. Runs nixos-install based on the provided flake +4. Reboots the machine. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/extra-files.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/extra-files.md new file mode 100644 index 0000000..ca067d4 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/extra-files.md @@ -0,0 +1,114 @@ +# Copying files to the new installation + +The `--extra-files <path>` option allows copying files to the target host after +installation. + +The contents of the `<path>` is recursively copied and overwrites the targets +root (/). The contents _must_ be in a structure and permissioned as it should be +on the target. + +In this way, there is no need to repeatedly pass arguments (eg: a fictional +argument: `--copy <source> <dest>`) to `nixos-anywhere` to complete the intended +outcome. + +The path and directory structure passed to `--extra-files` should be prepared +beforehand. + +This allows a simple programmatic invocation of `nixos-anywhere` for multiple +hosts. + +## Simple Example + +You want `/etc/ssh/ssh_host_*` and `/persist` from the local system on the +target. The `<path>` contents will look like this: + +```console +$ cd /tmp +$ root=$(mktemp -d) +$ sudo cp --verbose --archive --parents /etc/ssh/ssh_host_* ${root} +$ cp --verbose --archive --link /persist ${root} +``` + +The directory structure would look like this: + +```console +drwx------ myuser1 users 20 tmp.d6nx5QUwPN +drwxr-xr-x root root 6 ├── etc +drwx------ myuser1 users 160 │ └── ssh +.rw------- root root 399 │ ├── ssh_host_ed25519_key +.rw-r--r-- root root 91 │ ├── ssh_host_ed25519_key.pub +drwxr-xr-x myuser1 users 22 └── persist +drwxr-xr-x myuser1 users 14 ├── all +drwxr-xr-x myuser1 users 22 │ ├── my +.rw-r--r-- myuser1 users 6 │ │ ├── test3 +drwxr-xr-x myuser1 users 10 │ │ └── things +.rw-r--r-- myuser1 users 6 │ │ └── test4 +.rw-r--r-- myuser1 users 6 │ └── test2 +drwxr-xr-x myuser1 users 0 ├── blah +.rw-r--r-- myuser1 users 6 └── test +``` + +**NOTE**: Permissions will be copied, but ownership on the target will be root. + +Then pass $root like: + +> nixos-anywhere --flake ".#" --extra-files $root --target-host root@newhost + +## Programmatic Example + +```sh +for host in host1 host2 host3; do + root="target/${host}" + install -d -m755 ${root}/etc/ssh + ssh-keygen -A -C root@${host} -f ${root} + nixos-anywhere --extra-files "${root}" --flake ".#${host}" --target-host "root@${host}" +done +``` + +## Considerations + +### Ownership + +The new system may have differing UNIX user and group id's for users created +during installation. + +When the files are extracted on the remote the copied data will be owned by +root. + +If you wish to change the ownership after the files are copied onto the system, +you can use the `--chown` option. + +For example, if you did `--chown /home/myuser/.ssh 1000:100`, this would equate +to running `chown -R /home/myuser/.ssh 1000:100` where the uid is 1000 and the +gid is 100. **Only do this when you can _guarantee_ what the uid and gid will +be.** + +### Symbolic Links + +Do not create symbolic links to reference data to copy. + +GNU `tar` is used to do the copy over ssh. It is an archival tool used to +re/store directory structures as is. Thus `tar` copies symbolic links created +with `ln -s` by default. It does not follow them to copy the underlying file. + +### Hard links + +**NOTE**: hard links can only be created on the same filesystem. + +If you have larger persistent data to copy to the target. GNU `tar` will copy +data referenced by hard links created with `ln`. A hard link does not create +another copy the data. + +To copy a directory tree to the new target you can use the `cp` command with the +`--link` option which creates hard links. + +#### Example + +```sh +cd /tmp +root=$(mktemp -d) +cp --verbose --archive --link --parents /persist/home/myuser ${root} +``` + +`--parents` will create the directory structure of the source at the +destination. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/ipv6.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/ipv6.md new file mode 100644 index 0000000..64eb7a6 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/ipv6.md @@ -0,0 +1,23 @@ +# NixOS-anywhere on IPv6-only targets + +As GitHub engineers still haven't enabled the IPv6 switch, the kexec image +hosted on GitHub, cannot be used unfortunately on IPv6-only hosts. However it is +possible to use an IPv6 proxy for GitHub content like that: + +``` +nixos-anywhere \ + --kexec https://gh-v6.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-noninteractive-x86_64-linux.tar.gz \ +... +``` + +This proxy is hosted by [numtide](https://numtide.com/). It also works for IPv4. + +Alternatively it is also possible to reference a local file: + +``` +nixos-anywhere \ + --kexec ./nixos-kexec-installer-noninteractive-x86_64-linux.tar.gz \ +... +``` + +This tarball will be then uploaded via sftp to the target. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/limited-ram.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/limited-ram.md new file mode 100644 index 0000000..0cbac65 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/limited-ram.md @@ -0,0 +1,29 @@ +# Kexec on Systems with Limited RAM + +When working with nixos-anywhere on systems with limited RAM (around 1GB), you +can use the `--no-disko-deps` option to reduce memory usage during installation. + +## How it works + +The `--no-disko-deps` option uploads only the disko partitioning script without +including its dependencies. This significantly reduces memory usage because: + +1. The installer normally stores all dependencies in memory +2. Partitioning tools can be quite large when bundled with their dependencies + +## Usage example + +```bash +nix run github:nix-community/nixos-anywhere -- --no-disko-deps --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +## Trade-off + +While this approach saves memory, it means the partitioning tools will be +whatever versions are available on the target system, rather than the specific +versions defined in your NixOS configuration. This could potentially lead to +version inconsistencies between the partitioning tools and the NixOS system +being installed. + +This trade-off is usually acceptable for memory-constrained environments where +installation would otherwise fail due to insufficient RAM. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/nix-path.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/nix-path.md new file mode 100644 index 0000000..1505af9 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/nix-path.md @@ -0,0 +1,57 @@ +# Nix-channels / `NIX_PATH` + +nixos-anywhere does not install channels onto the new system by default to save +time and disk space. This for example results in errors like: + +``` +(stack trace truncated; use '--show-trace' to show the full trace) + +error: file 'nixpkgs' was not found in the Nix search path (add it using $NIX_PATH or -I) + +at «none»:0: (source not available) +``` + +when using tools like nix-shell/nix-env that rely on `NIX_PATH` being set. + +# Solution 1: Set the `NIX_PATH` via nixos configuration (recommended) + +Instead of stateful channels, one can also populate the `NIX_PATH` using nixos +configuration instead: + +```nix +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + # ... other inputs + + outputs = inputs@{ nixpkgs, ... }: + { + nixosConfigurations.yoursystem = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; # adapt to your actual system + modules = [ + # This line will populate NIX_PATH + { nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ]; } + # ... other modules and your configuration.nix + ]; + }; + }; +} +``` + +Advantage: This solution will be automatically kept up-to-date every time the +flake is updated. + +In your shell you will see something in your `$NIX_PATH`: + +```shellSession +$ echo $NIX_PATH +/root/.nix-defexpr/channels:nixpkgs=/nix/store/8b61j28rpy11dg8hanbs2x710d8w3v0d-source +``` + +# Solution 2: Manually add the channel + +On the installed machine, run: + +```shellSession +$ nix-channel --add https://nixos.org/channels/nixos-unstable nixos +$ nix-channel --update +``` diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/no-os.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/no-os.md new file mode 100644 index 0000000..1c07cb0 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/no-os.md @@ -0,0 +1,71 @@ +# Installing on a machine with no operating system + +If your machine doesn't currently have an operating system installed, you can +still run `nixos-anywhere` remotely to automate the install. To do this, you +would first need to boot the target machine from the standard NixOS installer. +You can either boot from a USB or use `netboot`. + +The +[NixOS installation guide](https://nixos.org/manual/nixos/stable/index.html#sec-booting-from-usb) +has detailed instructions on how to boot the installer. + +When you run `nixos-anywhere`, it will determine whether a NixOS installer is +present by checking whether the `/etc/os-release` file contains the identifier +`VARIANT_ID=installer`. This identifier is available on releases NixOS 23.05 or +later. + +If an installer is detected, `nixos-anywhere` will not attempt to `kexec` into +its own image. This is particularly useful for targets that don't have enough +RAM for `kexec` or don't support `kexec`. + +NixOS starts an SSH server on the installer by default, but you need to set a +password in order to access it. To set a password for the `nixos` user, run the +following command in a terminal on the NixOS machine: + +``` +passwd +``` + +If you don't know the IP address of the installer on your network, you can find +it by running the following command: + +``` +$ ip addr +1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 + link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff + altname enp0s3 + altname ens3 + inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0 + valid_lft 86385sec preferred_lft 75585sec + inet6 fec0::5054:ff:fe12:3456/64 scope site dynamic mngtmpaddr noprefixroute + valid_lft 86385sec preferred_lft 14385sec + inet6 fe80::5054:ff:fe12:3456/64 scope link + valid_lft forever preferred_lft forever +``` + +This will display the IP addresses assigned to your network interface(s), +including the IP address of the installer. In the example output below, the +installer's IP addresses are `10.0.2.15`, `fec0::5054:ff:fe12:3456`, and +`fe80::5054:ff:fe12:3456%eth0`: + +To test if you can connect and your password works, you can use the following +SSH command (replace the IP address with your own): + +``` +ssh -v nixos@fec0::5054:ff:fe12:3456 +``` + +You can then use the IP address to run `nixos-anywhere` like this: + +``` +nix run github:nix-community/nixos-anywhere -- --flake '.#myconfig' --target-host nixos@fec0::5054:ff:fe12:3456 +``` + +This example assumes a flake in the current directory containing a configuration +named `myconfig`. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/secrets.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/secrets.md new file mode 100644 index 0000000..8d95ee7 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/secrets.md @@ -0,0 +1,76 @@ +# Secrets and full disk encryption + +The `nixos-anywhere` utility offers the capability to install secrets onto a +target machine. This feature is particularly beneficial when you want to +bootstrap secrets management tools such as +[sops-nix](https://github.com/Mic92/sops-nix) or +[agenix](https://github.com/ryantm/agenix), which rely on machine-specific +secrets to decrypt other uploaded secrets. + +## Example: Decrypting an OpenSSH Host Key with pass + +In this example, we demonstrate how to use a script to decrypt an OpenSSH host +key from the `pass` password manager and subsequently pass it to +`nixos-anywhere` during the installation process: + +```bash +#!/usr/bin/env bash + +# Create a temporary directory +temp=$(mktemp -d) + +# Function to cleanup temporary directory on exit +cleanup() { + rm -rf "$temp" +} +trap cleanup EXIT + +# Create the directory where sshd expects to find the host keys +install -d -m755 "$temp/etc/ssh" + +# Decrypt your private key from the password store and copy it to the temporary directory +pass ssh_host_ed25519_key > "$temp/etc/ssh/ssh_host_ed25519_key" + +# Set the correct permissions so sshd will accept the key +chmod 600 "$temp/etc/ssh/ssh_host_ed25519_key" + +# Install NixOS to the host system with our secrets +nixos-anywhere --extra-files "$temp" --flake '.#your-host' --target-host root@yourip +``` + +## Example: Uploading Disk Encryption Secrets + +In a similar vein, `nixos-anywhere` can upload disk encryption secrets, which +are necessary during formatting with disko. Here's an example that demonstrates +how to provide your disk encryption password as a file or via the `pass` utility +to `nixos-anywhere`: + +```bash +# Write your disk encryption password to a file +echo "my-super-safe-password" > /tmp/disk-1.key + +# Call nixos-anywhere with disk encryption keys +nixos-anywhere \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(pass my-disk-encryption-password) \ + --flake '.#your-host' \ + root@yourip +``` + +In the above example, replace `"my-super-safe-password"` with your actual +encryption password, and `my-disk-encryption-password` with the relevant entry +in your pass password store. Also, ensure to replace `'.#your-host'` and +`root@yourip` with your actual flake and IP address, respectively. + +## Example: Using existing SSH host keys + +If the system contains existing trusted `/etc/ssh/ssh_host_*` SSH host keys and +certificates, `nixos-anywhere` can copy them in case they are necessary during +installation and system activation. + +``` +nixos-anywhere --copy-host-keys --flake '.#your-host' root@yourip +``` + +This would copy `/etc/ssh/ssh_host_*` to `/mnt` after kexec but before +installation, ignoring files that already exist in destination. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/terraform.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/terraform.md new file mode 100644 index 0000000..8797493 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/terraform.md @@ -0,0 +1,23 @@ +# Terraform + +The nixos-anywhere terraform modules allow you to use Terraform for installing +and updating NixOS. It simplifies the deployment process by integrating +nixos-anywhere functionality. + +Our terraform module requires the +[null](https://registry.terraform.io/providers/hashicorp/null/latest) and +[external](https://registry.terraform.io/providers/hashicorp/external/latest) +provider. + +You can get these by from nixpkgs like this: + +```nix +nix-shell -p '(pkgs.terraform.withPlugins (p: [ p.null p.external ]))' +``` + +You can add this expression the `packages` list in your devshell in flake.nix or +in shell.nix. + +Checkout out the +[module reference](https://github.com/nix-community/nixos-anywhere/tree/main/terraform) +for examples and module parameter on how to use the modules. diff --git a/launch/.terraform/modules/peertube.deploy/docs/howtos/use-without-flakes.md b/launch/.terraform/modules/peertube.deploy/docs/howtos/use-without-flakes.md new file mode 100644 index 0000000..33419a9 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/howtos/use-without-flakes.md @@ -0,0 +1,82 @@ +# Use without flakes + +First, +[import the disko NixOS module](https://github.com/nix-community/disko/blob/master/docs/HowTo.md#installing-nixos-module) +in your NixOS configuration and define disko devices as described in the +[examples](https://github.com/nix-community/disko/tree/master/example). + +Let's assume that your NixOS configuration lives in `configuration.nix` and your +target machine is called `machine`: + +## 1. Download your favourite disk layout: + +See https://github.com/nix-community/disko-templates/ for more examples: + +The example below will work with both UEFI and BIOS-based systems. + +```bash +curl https://raw.githubusercontent.com/nix-community/disko-templates/main/single-disk-ext4/disko-config.nix > ./disko-config.nix +``` + +## 2. Get a hardware-configuration.nix from on the target machine + +- **Option 1**: If NixOS is not installed, boot into an installer without first + installing NixOS. +- **Option 2**: Use the kexec tarball method, as described + [here](https://github.com/nix-community/nixos-images#kexec-tarballs). + +- **Generate Configuration**: Run the following command on the target machine: + + ```bash + nixos-generate-config --no-filesystems --dir /tmp/config + ``` + +This creates the necessary configuration files under `/tmp/config/`. Copy +`/tmp/config/nixos/hardware-configuration.nix` to your local machine into the +same directory as `disko-config.nix`. + +## 3. Set NixOS version to use + +```nix +# default.nix +let + # replace nixos-24.11 with your preferred nixos version or revision from here: https://status.nixos.org/ + nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-24.11.tar.gz"; +in +import (nixpkgs + "/nixos/lib/eval-config.nix") { + modules = [ ./configuration.nix ]; +} +``` + +## 4. Write a NixOS configuration + +```nix +# configuration.nix +{ + imports = [ + "${fetchTarball "https://github.com/nix-community/disko/tarball/master"}/module.nix" + ./disko-config.nix + ./hardware-configuration.nix + ]; + # Replace this with the system of the installation target you want to install!!! + disko.devices.disk.main.device = "/dev/sda"; + + # Set this to the NixOS version that you have set in the previous step. + # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . + system.stateVersion = "24.11"; +} +``` + +## 5. Build and deploy with nixos-anywhere + +Your current directory now should contain the following files from the previous +step: + +- `configuration.nix`, `default.nix`, `disko-config.nix` and + `hardware-configuration.nix` + +Run `nixos-anywhere` as follows: + +```bash +nixos-anywhere --store-paths $(nix-build -A config.system.build.formatScript -A config.system.build.toplevel --no-out-link) root@machine +``` diff --git a/launch/.terraform/modules/peertube.deploy/docs/logo.png b/launch/.terraform/modules/peertube.deploy/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..434ad443ac1a065b53f74353187f7baaa45bcf29 GIT binary patch literal 29405 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 F{2%b`V#WXf literal 0 HcmV?d00001 diff --git a/launch/.terraform/modules/peertube.deploy/docs/logo.svg b/launch/.terraform/modules/peertube.deploy/docs/logo.svg new file mode 100644 index 0000000..204dde7 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/logo.svg @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="512" + height="512" + id="svg322" + sodipodi:docname="logo.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + inkscape:export-filename="logo.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#"> + <defs + id="defs326"> + <marker + markerWidth="512" + markerHeight="512" + refX="256" + refY="256" + orient="auto" + id="marker11007"> + <path + d="M 0,0 H 512 V 512 H 0 Z" + fill="#fbfdfc" + id="path246" /> + </marker> + </defs> + <sodipodi:namedview + id="namedview324" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:showpageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="2.2304247" + inkscape:cx="32.280848" + inkscape:cy="129.79591" + inkscape:window-width="3840" + inkscape:window-height="2090" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg322" /> + <path + d="M0,0 L4,2 L18,10 L27,18 L30,26 L30,34 L27,46 L23,60 L17,73 L8,94 L3,105 L3,116 L7,119 L13,121 L39,126 L55,131 L64,137 L68,142 L70,146 L70,154 L66,162 L57,170 L45,176 L37,179 L20,182 L11,183 L-17,184 L-24,192 L-34,206 L-47,221 L-59,232 L-73,241 L-85,246 L-93,248 L-113,248 L-127,245 L-132,243 L-132,241 L-123,235 L-113,226 L-105,218 L-96,205 L-88,189 L-82,168 L-80,136 L-72,125 L-54,98 L-38,71 L-23,44 L-13,25 L-5,9 Z " + fill="#517ED0" + transform="translate(435,104)" + id="path248" /> + <path + d="M0,0 L10,1 L19,6 L31,17 L40,26 L49,37 L57,48 L67,62 L75,74 L91,75 L107,78 L127,85 L141,93 L153,105 L160,115 L167,130 L170,139 L171,149 L162,146 L155,143 L138,139 L110,139 L93,143 L87,146 L80,146 L71,144 L57,142 L22,140 L-23,140 L-45,141 L-65,143 L-74,143 L-81,135 L-83,129 L-83,117 L-79,109 L-73,102 L-63,95 L-46,87 L-28,82 L-11,79 L14,79 L11,71 L-2,48 L-10,34 L-14,23 L-14,13 L-11,7 L-5,2 Z " + fill="#517DD0" + transform="translate(132,13)" + id="path250" /> + <path + d="M0,0 L11,2 L20,5 L29,7 L55,7 L72,4 L80,2 L104,7 L126,9 L142,10 L234,10 L240,14 L245,20 L246,24 L246,33 L241,43 L234,50 L221,58 L205,65 L183,70 L162,73 L151,73 L159,87 L174,112 L179,122 L180,125 L180,136 L175,145 L167,150 L153,150 L143,143 L130,130 L117,113 L107,99 L95,80 L90,76 L71,74 L50,69 L34,62 L23,54 L15,46 L8,34 L2,17 L0,7 Z " + fill="#4DB0CE" + transform="translate(213,350)" + id="path252" /> + <path + d="M0,0 L2,0 L7,17 L12,30 L21,45 L31,57 L39,64 L46,71 L47,73 L49,89 L54,107 L60,122 L72,147 L83,167 L99,191 L113,212 L117,220 L117,234 L114,240 L111,243 L80,243 L63,231 L50,219 L38,207 L31,199 L30,196 L27,196 L25,200 L11,215 L2,224 L-9,230 L-13,231 L-21,231 L-27,227 L-32,221 L-36,210 L-36,203 L-28,186 L-22,174 L-17,159 L-13,141 L-12,131 L-12,99 L-16,64 L-18,49 L-18,34 L-15,24 L-8,11 Z " + fill="#517ED0" + transform="translate(158,269)" + id="path256" /> + <path + d="M0,0 L12,1 L28,8 L40,16 L50,24 L65,37 L75,48 L77,51 L79,51 L89,37 L98,27 L107,18 L117,14 L126,14 L134,19 L139,25 L142,32 L142,40 L137,54 L128,74 L123,88 L118,109 L117,117 L117,147 L121,169 L124,181 L124,200 L120,211 L111,225 L106,229 L100,214 L92,199 L80,183 L70,173 L64,168 L62,153 L57,139 L49,123 L34,97 L21,75 L6,53 L-6,36 L-12,26 L-13,23 L-13,11 L-9,5 L-4,1 Z " + fill="#4EB0CF" + transform="translate(246,0)" + id="path258" /> + <path + d="M0,0 L6,0 L6,2 L11,1 L22,1 L32,6 L41,8 L53,8 L60,16 L62,17 L61,22 L56,28 L54,33 L54,43 L58,45 L58,48 L52,47 L46,42 L46,40 L30,45 L24,53 L24,61 L25,62 L31,62 L35,58 L39,59 L41,62 L43,71 L44,72 L50,72 L53,80 L53,82 L61,80 L65,78 L70,78 L72,80 L82,79 L92,84 L103,95 L106,98 L108,99 L117,100 L123,105 L123,114 L118,128 L110,142 L99,156 L88,165 L77,173 L60,180 L55,180 L56,176 L65,170 L67,166 L67,151 L72,144 L73,141 L73,134 L69,129 L65,127 L55,126 L47,116 L45,112 L45,102 L49,97 L55,94 L55,87 L49,83 L44,78 L34,72 L18,64 L11,58 L4,42 L0,35 L1,26 L2,19 L-2,16 L-8,13 L-9,12 L-9,6 L-6,2 Z " + fill="#4D99CF" + transform="translate(223,166)" + id="path260" /> + <path + d="M0,0 L9,0 L20,8 L36,14 L44,17 L46,20 L46,27 L42,33 L42,44 L47,49 L46,52 L39,52 L25,46 L15,37 L6,27 L0,17 L-3,10 L-2,2 Z " + fill="#4FABD1" + transform="translate(176,284)" + id="path262" + style="fill:#4d99cf;fill-opacity:1" /> + <path + d="M0,0 L14,0 L23,1 L29,5 L30,6 L30,11 L25,10 L24,9 L24,4 L-1,4 Z " + fill="#438ED7" + transform="translate(247,157)" + id="path266" /> + <path + id="path268" + transform="translate(203,179)" + d="m 0,0 3,1 1,8 h 8 v 4 l -2,1 H 2 L -2,9 V 2 Z" + style="fill:#4d99cf;fill-opacity:1" /> + <path + id="path288" + transform="translate(278,222)" + d="M 0,0 6,1 7,4 9,5 H 2 L -1,3 Z m 0,0 6,1 6,2 8,1 V 7 H 7 L 0,4 Z" + style="fill:#4d99cf;fill-opacity:1" /> + <path + d="M0,0 L3,1 L-7,8 L-9,8 L-9,6 L-5,5 L-4,2 Z " + fill="#6184C5" + transform="translate(371,336)" + id="path304" + style="fill:#517ed0;fill-opacity:1" /> + <path + id="path314" + style="fill:#4eb0cf;fill-opacity:1" + transform="translate(113,201)" + d="M 0,0 H 2 L 0,4 -2.8602037,5.4301018 -4,6 V 8 L -6,7 Z m 48,-40 h 25 l 14,3 4,2 -1,3 -10,8 -10,11 -9,14 -7,14 -5,17 -2,11 -1,15 -8,10 -12,21 -11,19 -6,10 -11,19 -12,22 -11,21 -9,20 -5,1 -10,-5 -8,-4 -4,-6 -1,-3 v -18 l 6,-26 5,-13 5,-12 6,-13 2,-5 v -6 l -5,-3 -29,-4 -18,-6 -10,-6 -5,-5 -2,-4 V 50 l 7,-8 10,-7 18,-6 10,-2 10,-1 18.016033,-0.600534 L -22,25 l 5,-5 8,-9 7,-9 8,-10 9,-10 9,-9 10,-7 8,-4 z" /> + <metadata + id="metadata148"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <cc:license + rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" /> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/publicdomain/zero/1.0/"> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + </cc:License> + </rdf:RDF> + </metadata> +</svg> diff --git a/launch/.terraform/modules/peertube.deploy/docs/quickstart.md b/launch/.terraform/modules/peertube.deploy/docs/quickstart.md new file mode 100644 index 0000000..5eb1a10 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/quickstart.md @@ -0,0 +1,334 @@ +# Quickstart Guide: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" width="150" height="150"> + +[Documentation Index](./INDEX.md) + +## Introduction + +This guide documents a simple installation of NixOS using **nixos-anywhere** on +a target machine running x86_64 Linux with +[kexec](https://man7.org/linux/man-pages/man8/kexec.8.html) support. The example +used in this guide installs NixOS on a Hetzner cloud machine. The configuration +may be different for some other instances. We will be including further examples +in the [How To Guide](./howtos/INDEX.md) as and when they are available. + +You will need: + +- A [flake](https://wiki.nixos.org/wiki/Flakes) that controls the actions to be + performed +- A disk configuration containing details of the file system that will be + created on the new server. +- A target machine that is reachable via SSH, either using keys or a password, + and the privilege to either log in directly as root or a user with + password-less sudo. + +**nixos-anywhere** doesn’t need to be installed. You can run it directly from +[the Github repository.](https://github.com/nix-community/nixos-anywhere) + +Details of the flake, the disk configuration and the CLI command are discussed +below. + +## Steps required to run nixos-anywhere + +### 1. Enable Flakes + +Check if your nix has flakes enabled by running `nix flake`. It will tell you if +it's not. To enable flakes, refer to the +[NixOS Wiki](https://wiki.nixos.org/wiki/Flakes#enable-flakes). + +### 2. Initialize a Flake + +The easiest way to start is to copy our +[example flake.nix](https://github.com/nix-community/nixos-anywhere-examples/blob/main/flake.nix) +into a new directory. This example is tailored for a virtual machine setup +similar to one on [Hetzner Cloud](https://www.hetzner.com/cloud), so you might +need to adapt it for your setup. + +If you already have a flake, you can use it by adding +[disko configuration](https://github.com/nix-community/disko?tab=readme-ov-file#how-to-use-disko) +to it. + +### 3. Configure your SSH key + +If you cloned +[our nixos-anywhere-example](https://github.com/nix-community/nixos-anywhere-examples/blob/main/configuration.nix) +you will also replace the SSH key like this: In your configuration, locate the +line that reads: + +```bash +# change this to your ssh key + "CHANGE" +``` + +Replace the text `CHANGE` with your own SSH key. This is crucial, as you will +not be able to log into the target machine post-installation without it. If you +have a .pem file you can run + +```bash +ssh-keygen -y -f /path/to/your/key.pem +``` + +then paste the result in between the quotes like "ssh-rsa AAA..." + +### 4. Configure Storage + +In the same directory, create a file called `disk-config.nix`. This file will +define the disk layout for the +[disko](https://github.com/nix-community/disko/blob/master/docs/INDEX.md) tool, +which is used by nixos-anywhere to partition, format, and mount the disks. + +For a basic installation, you can copy the contents from the example provided +[here](https://github.com/nix-community/nixos-anywhere-examples/blob/main/disk-config.nix). +This configuration sets up a standard GPT (GUID Partition Table) that is +compatible with both EFI and BIOS systems and mounts the disk as `/dev/sda`. You +may need to adjust `/dev/sda` to match the correct disk on your machine. To +identify the disk, run the `lsblk` command and replace `sda` with the actual +disk name. + +For example, on this machine, we would select `/dev/nvme0n1` as the disk: + +``` +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS +nvme0n1 259:0 0 1.8T 0 disk +``` + +If this setup does not match your requirements, you can choose an example that +better suits your disk layout from the +[disko examples](https://github.com/nix-community/disko/tree/master/example). +For more detailed information, refer to the +[disko documentation](https://github.com/nix-community/disko). + +### 5. Lock your Flake + +``` +nix flake lock +``` + +This will download your flake dependencies and make a `flake.lock` file that +describes how to reproducibly build your system. + +Optionally, you can commit these files to a repo such as Github, or you can +simply reference your local directory when you run **nixos-anywhere**. This +example uses a local directory on the source machine. + +### 6. Connectivity to the Target Machine + +**nixos-anywhere** will create a temporary SSH key to use for the installation. +If your SSH key is not found, you will be asked for your password. If you are +using a non-root user, you must have access to sudo without a password. To avoid +SSH password prompts, set the `SSHPASS` environment variable to your password +and add `--env-password` to the `nixos-anywhere` command. If providing a +specific SSH key through `-i` (identity_file), this key will then be used for +the installation and no temporary SSH key will be created. + +### 7. (Optional) Test your NixOS and Disko configuration + +Skip this step and continue with Step 8, if you don't have a hardware +configuration (hardware-configuration.nix or facter.json) generated yet or make +sure you don't import non-existing hardware-configuration.nix or facter.json +during running the vm test. + +The following command will automatically test your nixos configuration and run +disko inside a virtual machine, where + +- `<path to configuration>` is the path to the directory or repository + containing `flake.nix` and `disk-config.nix` + +- `<configuration name>` must match the name that immediately follows the text + `nixosConfigurations.` in the flake, as indicated by the comment in the + [example](https://github.com/nix-community/nixos-anywhere-examples/blob/main/flake.nix). + +``` +nix run github:nix-community/nixos-anywhere -- --flake <path to configuration>#<configuration name> --vm-test +``` + +### 8. Prepare Hardware Configuration + +If you're not using a virtual machine, it's recommended to allow +`nixos-anywhere` to generate a hardware configuration during installation. This +ensures that essential drivers, such as those required for disk detection, are +properly configured. + +To enable `nixos-anywhere` to integrate its generated configuration into your +NixOS setup, you need to include an import for the hardware configuration +beforehand. + +Here’s an example: + +```diff + nixosConfigurations.generic = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + disko.nixosModules.disko + ./configuration.nix ++ ./hardware-configuration.nix + ]; + }; +``` + +When running `nixos-anywhere`, this file is automatically generated by including +the following flags in your command: +`--generate-hardware-config nixos-generate-config ./hardware-configuration.nix`. +The second flag, `./hardware-configuration.nix`, specifies where +`nixos-generate-config` will store the configuration. Adjust this path to +reflect the location where you want the `hardware-configuration.nix` for this +machine to be saved. + +#### 8.1 nixos-facter + +As an alternative to `nixos-generate-config`, you can use the experimental +[nixos-facter](https://github.com/numtide/nixos-facter) command, which offers +more comprehensive hardware reports and advanced configuration options. + +To use `nixos-facter`, add the following to your flake inputs: + +```diff + { ++ inputs.nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; + } +``` + +Next, import the module into your configuration and specify `facter.json` as the +path where the hardware report will be saved: + +```diff + nixosConfigurations.generic-nixos-facter = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + disko.nixosModules.disko + ./configuration.nix ++ nixos-facter-modules.nixosModules.facter ++ { config.facter.reportPath = ./facter.json } + ]; + }; +``` + +To generate the configuration for `nixos-facter` with `nixos-anywhere`, use the +following flags: `--generate-hardware-config nixos-facter ./facter.json`. The +second flag, `./facter.json`, specifies where `nixos-generate-config` will store +the hardware report. Adjust this path to suit the location where you want the +`facter.json` to be saved. + +### 9. Run it + +You can now run **nixos-anywhere** from the command line as shown below, where: + +- `<path to configuration>` is the path to the directory or repository + containing `flake.nix` and `disk-config.nix` + +- `<configuration name>` must match the name that immediately follows the text + `nixosConfigurations.` in the flake, as indicated by the comment in the + [example](https://github.com/nix-community/nixos-anywhere-examples/blob/main/flake.nix). + +- `<ip address>` is the IP address of the target machine. + +``` +nix run github:nix-community/nixos-anywhere -- --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +The command would look like this if you had created your files in a directory +named `/home/mydir/test` and the IP address of your target machine is +`37.27.18.135`: + +``` +nix run github:nix-community/nixos-anywhere -- --flake /home/mydir/test#hetzner-cloud --target-host root@37.27.18.135 +``` + +If you also need to generate hardware configuration amend flags for +nixos-generate-config: + +``` +nix run github:nix-community/nixos-anywhere -- --generate-hardware-config nixos-generate-config ./hardware-configuration.nix --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +Or these flags if you are using nixos-facter instead: + +``` +nix run github:nix-community/nixos-anywhere -- --generate-hardware-config nixos-facter ./facter.json --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +Adjust the location of `./hardware-configuration.nix` and `./facter.json` +accordingly. + +**nixos-anywhere** will then run, showing various output messages at each stage. +It may take some time to complete, depending on Internet speeds. It should +finish by showing the messages below before returning to the command prompt. + +``` +Installation finished. No error reported. +Warning: Permanently added '<ip-address>' (ED25519) to the list of known hosts +``` + +When this happens, the target server will have been overwritten with a new +installation of NixOS. Note that the server's public SSH key will have changed. + +If you have previously accessed this server using SSH, you may see the following +message the next time you try to log in to the target. + +``` +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! +Someone could be eavesdropping on you right now (man-in-the-middle attack)! +It is also possible that a host key has just been changed. +The fingerprint for the ED25519 key sent by the remote host is +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. +Please contact your system administrator. +Add correct host key in ~/.ssh/known_hosts to get rid of this message. +Offending ECDSA key in ~/.ssh/known_hosts:6 + remove with: + ssh-keygen -f ~/.ssh/known_hosts" -R "<ip address>" +Host key for <ip_address> has changed and you have requested strict checking. +Host key verification failed. +``` + +This is because the `known_hosts` file in the `.ssh` directory now contains a +mismatch, since the server has been overwritten. To solve this, use a text +editor to remove the old entry from the `known_hosts` file (or use the command +`ssh-keygen -R <ip_address>`). The next connection attempt will then treat this +as a new server. + +The error message line `Offending ECDSA key in ~/.ssh/known_hosts:6` gives the +line number that needs to be removed from the `known_hosts` file (line 6 in this +example). + +# Finished! + +**nixos-anywhere**'s job is now done, as it is a tool to install NixOS onto the +target machine. + +Any future changes to the configuration should be made to your flake. You would +reference this flake when using the NixOS `nixos-rebuild` command or a separate +3rd party deployment tool of your choice i.e. +[deploy-rs](https://github.com/serokell/deploy-rs), +[colmena](https://github.com/zhaofengli/colmena), +[nixinate](https://github.com/MatthewCroughan/nixinate), +[clan](https://clan.lol/) (author's choice). + +To update on the machine locally (replace `<URL to your flake>` with your flake +i.e. `.#` if your flake is in the current directory): + +``` +nixos-rebuild switch --flake <URL to your flake> +``` + +To update remotely you will need to have configured an +[ssh server](https://search.nixos.org/options?show=services.sshd.enable) and +your ssh key for the +[root user](https://search.nixos.org/options?show=users.users.%3Cname%3E.openssh.authorizedKeys.keys): + +``` +nixos-rebuild switch --flake <URL to your flake> --target-host "root@<ip address>" +``` + +See the Nix documentation for use of the flake +[URL-like syntax](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake#url-like-syntax). + +For more information on different use cases of **nixos-anywhere** please refer +to the [How to Guide](./howtos/INDEX.md), and for more technical information and +explanation of known error messages, refer to the +[Reference Manual](./reference.md). diff --git a/launch/.terraform/modules/peertube.deploy/docs/reference.md b/launch/.terraform/modules/peertube.deploy/docs/reference.md new file mode 100644 index 0000000..8391657 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/reference.md @@ -0,0 +1,137 @@ +# Reference Manual: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="141"> + +[Documentation Index](./INDEX.md) + +TODO: Populate this guide properly + +## Contents + +[Command Line Usage](#command-line-usage) + +[Explanation of known error messages](#explanation-of-known-error-messages) + +## Command Line Usage + +<!-- `$ bash ./src/nixos-anywhere.sh --help` --> + +``` +Usage: nixos-anywhere [options] [<ssh-host>] + +Options: + +* -f, --flake <flake_uri> + set the flake to install the system from. i.e. + nixos-anywhere --flake .#mymachine + Also supports variants: + nixos-anywhere --flake .#nixosConfigurations.mymachine.config.virtualisation.vmVariant +* --target-host <ssh-host> + set the SSH target host to deploy onto. +* -i <identity_file> + selects which SSH private key file to use. +* -p, --ssh-port <ssh_port> + set the ssh port to connect with +* --ssh-option <ssh_option> + set an ssh option +* -L, --print-build-logs + print full build logs +* --env-password + set a password used by ssh-copy-id, the password should be set by + the environment variable SSHPASS +* -s, --store-paths <disko-script> <nixos-system> + set the store paths to the disko-script and nixos-system directly + if this is given, flake is not needed +* --kexec <path> + use another kexec tarball to bootstrap NixOS +* --kexec-extra-flags + extra flags to add into the call to kexec, e.g. "--no-sync" +* --ssh-store-setting <key> <value> + ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded. +* --post-kexec-ssh-port <ssh_port> + after kexec is executed, use a custom ssh port to connect. Defaults to 22 +* --copy-host-keys + copy over existing /etc/ssh/ssh_host_* host keys to the installation +* --extra-files <path> + contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten + Copied files will be owned by root unless specified by --chown option. See documentation for details. +* --chown <path> <ownership> + change ownership of <path> recursively. Recommended to use uid:gid as opposed to username:groupname for ownership. + Option can be specified more than once. +* --disk-encryption-keys <remote_path> <local_path> + copy the contents of the file or pipe in local_path to remote_path in the installer environment, + after kexec but before installation. Can be repeated. +* --no-substitute-on-destination + disable passing --substitute-on-destination to nix-copy +* --debug + enable debug output +* --show-trace + show nix build traces +* --option <key> <value> + nix option to pass to every nix related command +* --from <store-uri> + URL of the source Nix store to copy the nixos and disko closure from +* --build-on-remote + build the closure on the remote machine instead of locally and copy-closuring it +* --vm-test + build the system and test the disk configuration inside a VM without installing it to the target. +* --generate-hardware-config nixos-facter|nixos-generate-config <path> + generate a hardware-configuration.nix file using the specified backend and write it to the specified path. + The backend can be either 'nixos-facter' or 'nixos-generate-config'. +* --phases + comma separated list of phases to run. Default is: kexec,disko,install,reboot + kexec: kexec into the nixos installer + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode + install: install the system + reboot: unmount the filesystems, export any ZFS pools and reboot the machine +* --disko-mode disko|mount|format + set the disko mode to format, mount or destroy. Default is disko. + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode +* --no-disko-deps + This will only upload the disko script and not the partitioning tools dependencies. + Installers usually have dependencies available. + Use this option if your target machine has not enough RAM to store the dependencies in memory. +* --build-on auto|remote|local + sets the build on settings to auto, remote or local. Default is auto. + auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build + local: will build on the local host + remote: will build on the remote host +``` + +## Explanation of known error messages + +TODO: Add additional error messages and meanings. Fill in missing explanations + +This section lists known error messages and their explanations. Some +explanations may refer to the following CLI syntax: + +`nix run github:nix-community/nixos-anywhere -- --flake <path to configuration>#<configuration name> root@<ip address>` + +This list is not comprehensive. It's possible you may encounter errors that +originate from the underlying operating system. These should be documented in +the relevant operating system manual. + +| Id | Message | Explanation | +| -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 1 | Failure unpacking initrd | You don't have enough RAM to hold `kexec` | +| 2 | Flake <flake_url> does not provide attribute | The configuration name you specified in your flake URI is not defined as a NixOS configuration in your flake eg if your URI was mydir#myconfig, then myconfig should be included in the flake as `nixosConfigurations.myconfig` | +| 3 | Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri. | As for error #2 | +| | For example, to use the output nixosConfigurations.foo from the flake.nix, append "#foo" to the flake-uri | | +| 4 | Retrieving host facts via ssh failed. Check with --debug for the root cause, unless you have done so already | TODO: Explain | +| 5 | ssh-host must be set | <ip_address> has not been supplied | +| 6 | <disko_script> and <nixos_system> must be existing store-paths | This occurs if the -s switch has been used to specify the disko script and store path correctly, and the scripts cannot be found at the given URI | +| 7 | flake must be set | This occurs if both the -flake option (use a flake) and the -s option (specify paths directly) have been omitted. Either one or the other must be specified. | +| 8 | no tar command found, but required to unpack kexec tarball | The destination machine does not have a `tar` command available. This is needed to unpack the `kexec`. | +| 9 | no setsid command found, but required to run the kexec script under a new session | The destination machine does not have the `setsid` command available | +| 10 | This script requires Linux as the operating system, but got <operating system> | The destination machine is not running Linux | +| 11 | The default kexec image only support x86_64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information. | By default, `nixos-anywhere` uses its own `kexec` image, which will only run on x86_64 CPUs. For other CPU types, you can use your own `kexec` image instead. Refer to the [How To Guide](./howtos#using-your-own-kexec-image) for instructions. | +| 12 | Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri. | This is a `disko` error. As for Error #2 | +| | For example, to use the output diskoConfigurations.foo from the flake.nix, append \"#foo\" to the flake-uri. | | +| 13 | mode must be either create, mount or zap_create_mount | This is a `disko` error. The `disko` switches have not been used correctly. This could happen if you supplied your own `disko` script using the -s option | +| 14 | disko config must be an existing file or flake must be set | This is a `disko` error. This will happen if the `disko.devices` entry in your flake doesn't match the name of a file in the same location as your flake. | +| | | | +| | | | +| | | | +| | | | diff --git a/launch/.terraform/modules/peertube.deploy/docs/requirements.md b/launch/.terraform/modules/peertube.deploy/docs/requirements.md new file mode 100644 index 0000000..67b14f4 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/docs/requirements.md @@ -0,0 +1,39 @@ +# System Requirements: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" width="150" height="150"> + +[Documentation Index](./INDEX.md) + +## Requirements + +### Source Machine + +1. **Supported Systems:** + - Linux or macOS computers with Nix installed. + - NixOS + - Windows systems using WSL2. + +2. **Nix Installation:** If Nix is not yet installed on your system, refer to + the [nix installation page](https://nixos.org/download#download-nix). + +### Destination Machine + +The machine must be reachable over the public internet or local network. +Nixos-anywhere does not support wifi networks. If a VPN is needed, define a +custom installer via the --kexec flag which connects to your VPN. + +1. **Direct Boot Option:** + - Must be already running a NixOS installer. + +2. **Alternative Boot Options:** If not booting directly from a NixOS installer + image: + - **Architecture & Support:** Must be operating on: + - x86-64 or aarch64 Linux systems with kexec support. Note: While most + x86-64 Linux systems support kexec, if you're using an architecture other + than those mentioned, you may need to specify a + [different kexec image](./howtos/INDEX.md#using-your-own-kexec-image) + manually. + - **Memory Requirements:** + - At least 1 GB of RAM (excluding swap space). diff --git a/launch/.terraform/modules/peertube.deploy/flake.lock b/launch/.terraform/modules/peertube.deploy/flake.lock new file mode 100644 index 0000000..5a7232c --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/flake.lock @@ -0,0 +1,132 @@ +{ + "nodes": { + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741786315, + "narHash": "sha256-VT65AE2syHVj6v/DGB496bqBnu1PXrrzwlw07/Zpllc=", + "owner": "nix-community", + "repo": "disko", + "rev": "0d8c6ad4a43906d14abd5c60e0ffe7b587b213de", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "master", + "repo": "disko", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741352980, + "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixos-images": { + "inputs": { + "nixos-stable": [ + "nixos-stable" + ], + "nixos-unstable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741866599, + "narHash": "sha256-Re/T1Cjmiis0tdphj/Wjqt+c2RlMw/il7LBWzvwQPz0=", + "owner": "nix-community", + "repo": "nixos-images", + "rev": "63285ff93fc1daa2caac9f86e2302ae4edc5e84f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-images", + "type": "github" + } + }, + "nixos-stable": { + "locked": { + "lastModified": 1741862977, + "narHash": "sha256-prZ0M8vE/ghRGGZcflvxCu40ObKaB+ikn74/xQoNrGQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "cdd2ef009676ac92b715ff26630164bb88fec4e0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1742051767, + "narHash": "sha256-JpyjnalnIqJ7cvP8HzaoJN9/i2bDx83dToodHHjGuNg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ec886d10b507760c90ed01e2eac7f0679d0a47ae", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable-small", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "disko": "disko", + "flake-parts": "flake-parts", + "nixos-images": "nixos-images", + "nixos-stable": "nixos-stable", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1739829690, + "narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "3d0579f5cc93436052d94b73925b48973a104204", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/launch/.terraform/modules/peertube.deploy/flake.nix b/launch/.terraform/modules/peertube.deploy/flake.nix new file mode 100644 index 0000000..d60191c --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/flake.nix @@ -0,0 +1,55 @@ +{ + description = "A universal nixos installer, just needs ssh access to the target system"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable-small"; + flake-parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + + # used for testing + disko = { + url = "github:nix-community/disko/master"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nixos-stable.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixos-images.url = "github:nix-community/nixos-images"; + nixos-images.inputs.nixos-unstable.follows = "nixpkgs"; + nixos-images.inputs.nixos-stable.follows = "nixos-stable"; + + # used for development + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs: + inputs.flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + "aarch64-darwin" + ]; + imports = [ + ./src/flake-module.nix + ./tests/flake-module.nix + ./docs/flake-module.nix + # allow to disable treefmt in downstream flakes + ] ++ inputs.nixpkgs.lib.optional (inputs.treefmt-nix ? flakeModule) ./treefmt/flake-module.nix; + + perSystem = + { self', lib, ... }: + { + checks = + let + packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages; + devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells; + in + packages // devShells; + }; + }; +} diff --git a/launch/.terraform/modules/peertube.deploy/scripts/create-release.sh b/launch/.terraform/modules/peertube.deploy/scripts/create-release.sh new file mode 100755 index 0000000..7f20303 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/scripts/create-release.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env nix +#! nix shell nixpkgs#bash nixpkgs#gnused --command bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$SCRIPT_DIR/.." + +version=${1:-} +if [[ -z $version ]]; then + echo "USAGE: $0 version" >&2 + exit 1 +fi + +if [[ "$(git symbolic-ref --short HEAD)" != "main" ]]; then + echo "must be on main branch" >&2 + exit 1 +fi + +# ensure we are up-to-date +uncommitted_changes=$(git diff --compact-summary) +if [[ -n $uncommitted_changes ]]; then + echo -e "There are uncommitted changes, exiting:\n${uncommitted_changes}" >&2 + exit 1 +fi +git pull git@github.com:nix-community/nixos-anywhere main +unpushed_commits=$(git log --format=oneline origin/main..main) +if [[ $unpushed_commits != "" ]]; then + echo -e "\nThere are unpushed changes, exiting:\n$unpushed_commits" >&2 + exit 1 +fi +sed -i -e "s!version = \".*\";!version = \"${version}\";!" src/default.nix +git add src/default.nix +nix-shell -p nix-fast-build --command "nix-fast-build --eval-workers 2" +git commit -m "bump version ${version}" +git tag "${version}" + +echo "now run 'git push --tags origin main'" diff --git a/launch/.terraform/modules/peertube.deploy/src/default.nix b/launch/.terraform/modules/peertube.deploy/src/default.nix new file mode 100644 index 0000000..c171932 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/src/default.nix @@ -0,0 +1,63 @@ +{ + stdenv, + openssh, + gitMinimal, + nixVersions, + nix, + coreutils, + curl, + gnugrep, + gnutar, + gawk, + findutils, + gnused, + sshpass, + terraform-docs, + lib, + makeWrapper, + mkShellNoCC, +}: +let + runtimeDeps = [ + gitMinimal # for git flakes + # pinned because nix-copy-closure hangs if ControlPath provided for SSH: https://github.com/NixOS/nix/issues/8480 + (if lib.versionAtLeast nix.version "2.16" then nix else nixVersions.nix_2_16) + coreutils + curl # when uploading tarballs + gnugrep + gawk + findutils + gnused # needed by ssh-copy-id + sshpass # used to provide password for ssh-copy-id + gnutar # used to upload extra-files + ]; +in +stdenv.mkDerivation { + pname = "nixos-anywhere"; + version = "1.8.0"; + src = ./..; + nativeBuildInputs = [ makeWrapper ]; + installPhase = '' + install -D --target-directory=$out/libexec/nixos-anywhere/ -m 0755 src/*.sh + + # We prefer the system's openssh over our own, since it might come with features not present in ours: + # https://github.com/nix-community/nixos-anywhere/issues/62 + makeShellWrapper $out/libexec/nixos-anywhere/nixos-anywhere.sh $out/bin/nixos-anywhere \ + --prefix PATH : ${lib.makeBinPath runtimeDeps} --suffix PATH : ${lib.makeBinPath [ openssh ]} + ''; + + # Dependencies for our devshell + passthru.devShell = mkShellNoCC { + packages = runtimeDeps ++ [ + openssh + terraform-docs + ]; + }; + + meta = with lib; { + description = "Install nixos everywhere via ssh"; + homepage = "https://github.com/nix-community/nixos-anywhere"; + license = licenses.mit; + platforms = platforms.all; + }; +} diff --git a/launch/.terraform/modules/peertube.deploy/src/flake-module.nix b/launch/.terraform/modules/peertube.deploy/src/flake-module.nix new file mode 100644 index 0000000..d131219 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/src/flake-module.nix @@ -0,0 +1,11 @@ +{ + perSystem = + { config, pkgs, ... }: + { + packages = { + nixos-anywhere = pkgs.callPackage ./. { }; + default = config.packages.nixos-anywhere; + }; + devShells.default = config.packages.nixos-anywhere.devShell; + }; +} diff --git a/launch/.terraform/modules/peertube.deploy/src/get-facts.sh b/launch/.terraform/modules/peertube.deploy/src/get-facts.sh new file mode 100755 index 0000000..8043442 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/src/get-facts.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -efu "${enableDebug:-}" +has() { + command -v "$1" >/dev/null && echo "y" || echo "n" +} +isNixos=$(if test -f /etc/os-release && grep -Eq 'ID(_LIKE)?="?nixos"?' /etc/os-release; then echo "y"; else echo "n"; fi) +cat <<FACTS +isOs=$(uname) +isArch=$(uname -m) +isKexec=$(if test -f /etc/is_kexec; then echo "y"; else echo "n"; fi) +isNixos=$isNixos +isInstaller=$(if [ "$isNixos" = "y" ] && grep -Eq 'VARIANT_ID="?installer"?' /etc/os-release; then echo "y"; else echo "n"; fi) +isContainer=$(if [ "$(has systemd-detect-virt)" = "y" ]; then systemd-detect-virt --container; else echo "none"; fi) +hasIpv6Only=$(if [ "$(has ip)" = "n" ] || ip r g 1 >/dev/null 2>/dev/null || ! ip -6 r g :: >/dev/null 2>/dev/null; then echo "n"; else echo "y"; fi) +hasTar=$(has tar) +hasCpio=$(has cpio) +hasSudo=$(has sudo) +hasDoas=$(has doas) +hasWget=$(has wget) +hasCurl=$(has curl) +hasSetsid=$(has setsid) +hasNixOSFacter=$(command -v nixos-facter >/dev/null && echo "y" || echo "n") +FACTS diff --git a/launch/.terraform/modules/peertube.deploy/src/nixos-anywhere.sh b/launch/.terraform/modules/peertube.deploy/src/nixos-anywhere.sh new file mode 100755 index 0000000..c6cadb9 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/src/nixos-anywhere.sh @@ -0,0 +1,880 @@ +#!/usr/bin/env bash +set -euo pipefail + +here=$(dirname "${BASH_SOURCE[0]}") +flake="" +flakeAttr="" +kexecUrl="" +kexecExtraFlags="" +sshStoreSettings="" +enableDebug="" +nixBuildFlags=() +diskoAttr="" +diskoScript="" +diskoMode="disko" +diskoDeps=y +nixosSystem="" +extraFiles="" +vmTest="n" +nixOptions=( + --extra-experimental-features 'nix-command flakes' + "--no-write-lock-file" +) +SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY-} + +declare -A phases +phases[kexec]=1 +phases[disko]=1 +phases[install]=1 +phases[reboot]=1 + +hardwareConfigBackend=none +hardwareConfigPath= +sshPrivateKeyFile= +if [ -t 0 ]; then # stdin is a tty, we allow interactive input to ssh i.e. passwords + sshTtyParam="-t" +else + sshTtyParam="-T" +fi +sshConnection= +postKexecSshPort=22 +buildOnRemote=n +buildOn=auto +envPassword=n + +# Facts set by get-facts.sh +isOs= +isArch= +isKexec= +isInstaller= +isContainer= +hasIpv6Only= +hasTar= +hasCpio= +hasSudo= +hasDoas= +hasWget= +hasCurl= +hasSetsid= +hasNixOSFacter= + +sshKeyDir=$(mktemp -d) +trap 'rm -rf "$sshKeyDir"' EXIT +mkdir -p "$sshKeyDir" + +declare -A diskEncryptionKeys=() +declare -A extraFilesOwnership=() +declare -a nixCopyOptions=() +declare -a sshArgs=() + +showUsage() { + cat <<USAGE +Usage: nixos-anywhere [options] [<ssh-host>] + +Options: + +* -f, --flake <flake_uri> + set the flake to install the system from. i.e. + nixos-anywhere --flake .#mymachine + Also supports variants: + nixos-anywhere --flake .#nixosConfigurations.mymachine.config.virtualisation.vmVariant +* --target-host <ssh-host> + set the SSH target host to deploy onto. +* -i <identity_file> + selects which SSH private key file to use. +* -p, --ssh-port <ssh_port> + set the ssh port to connect with +* --ssh-option <ssh_option> + set an ssh option +* -L, --print-build-logs + print full build logs +* --env-password + set a password used by ssh-copy-id, the password should be set by + the environment variable SSHPASS +* -s, --store-paths <disko-script> <nixos-system> + set the store paths to the disko-script and nixos-system directly + if this is given, flake is not needed +* --kexec <path> + use another kexec tarball to bootstrap NixOS +* --kexec-extra-flags + extra flags to add into the call to kexec, e.g. "--no-sync" +* --ssh-store-setting <key> <value> + ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded. +* --post-kexec-ssh-port <ssh_port> + after kexec is executed, use a custom ssh port to connect. Defaults to 22 +* --copy-host-keys + copy over existing /etc/ssh/ssh_host_* host keys to the installation +* --extra-files <path> + contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten + Copied files will be owned by root unless specified by --chown option. See documentation for details. +* --chown <path> <ownership> + change ownership of <path> recursively. Recommended to use uid:gid as opposed to username:groupname for ownership. + Option can be specified more than once. +* --disk-encryption-keys <remote_path> <local_path> + copy the contents of the file or pipe in local_path to remote_path in the installer environment, + after kexec but before installation. Can be repeated. +* --no-substitute-on-destination + disable passing --substitute-on-destination to nix-copy +* --debug + enable debug output +* --show-trace + show nix build traces +* --option <key> <value> + nix option to pass to every nix related command +* --from <store-uri> + URL of the source Nix store to copy the nixos and disko closure from +* --build-on-remote + build the closure on the remote machine instead of locally and copy-closuring it +* --vm-test + build the system and test the disk configuration inside a VM without installing it to the target. +* --generate-hardware-config nixos-facter|nixos-generate-config <path> + generate a hardware-configuration.nix file using the specified backend and write it to the specified path. + The backend can be either 'nixos-facter' or 'nixos-generate-config'. +* --phases + comma separated list of phases to run. Default is: kexec,disko,install,reboot + kexec: kexec into the nixos installer + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode + install: install the system + reboot: unmount the filesystems, export any ZFS pools and reboot the machine +* --disko-mode disko|mount|format + set the disko mode to format, mount or destroy. Default is disko. + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode +* --no-disko-deps + This will only upload the disko script and not the partitioning tools dependencies. + Installers usually have dependencies available. + Use this option if your target machine has not enough RAM to store the dependencies in memory. +* --build-on auto|remote|local + sets the build on settings to auto, remote or local. Default is auto. + auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build + local: will build on the local host + remote: will build on the remote host +USAGE +} + +abort() { + echo "aborted: $*" >&2 + exit 1 +} + +step() { + echo "### $* ###" +} + +parseArgs() { + local substituteOnDestination=y + local printBuildLogs=n + local buildOnRemote=n + while [[ $# -gt 0 ]]; do + case "$1" in + -f | --flake) + flake=$2 + shift + ;; + --target-host) + sshConnection=$2 + shift + ;; + -i) + sshPrivateKeyFile=$2 + shift + ;; + -p | --ssh-port) + sshArgs+=("-p" "$2") + shift + ;; + --ssh-option) + sshArgs+=("-o" "$2") + shift + ;; + -L | --print-build-logs) + printBuildLogs=y + ;; + -s | --store-paths) + diskoScript=$(readlink -f "$2") + nixosSystem=$(readlink -f "$3") + shift + shift + ;; + --generate-hardware-config) + if [[ $# -lt 3 ]]; then + abort "Missing arguments for --generate-hardware-config <backend> <path>" + fi + case "$2" in + nixos-facter | nixos-generate-config) + hardwareConfigBackend=$2 + ;; + *) + abort "Unknown hardware config backend: $2" + ;; + esac + hardwareConfigPath=$3 + shift + shift + ;; + -t | --tty) + echo "the '$1' flag is deprecated, a tty is now detected automatically" >&2 + ;; + --help) + showUsage + exit 0 + ;; + --kexec) + kexecUrl=$2 + shift + ;; + --kexec-extra-flags) + kexecExtraFlags=$2 + shift + ;; + --ssh-store-setting) + key=$2 + shift + value=$2 + shift + sshStoreSettings+="$sshStoreSettings$key=$value&" + shift + ;; + --post-kexec-ssh-port) + postKexecSshPort=$2 + shift + ;; + --copy-host-keys) + copyHostKeys=y + ;; + --show-trace) + nixBuildFlags+=("--show-trace") + ;; + --debug) + enableDebug="-x" + printBuildLogs=y + set -x + ;; + --disko-mode) + case "$2" in + format | mount | disko) + diskoMode=$2 + ;; + *) + abort "Supported values for --disko-mode are disko, mount and format. Unknown mode : $2" + ;; + esac + + shift + ;; + --no-disko-deps) + diskoDeps=n + ;; + --build-on) + case "$2" in + auto | local | remote) + buildOn=$2 + ;; + *) + abort "Supported values for --build-on are auto, local and remote. Unknown mode : $2" + ;; + esac + + shift + ;; + --extra-files) + extraFiles=$2 + shift + ;; + --chown) + extraFilesOwnership["$2"]="$3" + shift + shift + ;; + --disk-encryption-keys) + diskEncryptionKeys["$2"]="$3" + shift + shift + ;; + --phases) + phases[kexec]=0 + phases[disko]=0 + phases[install]=0 + phases[reboot]=0 + IFS=, read -r -a phaseList <<<"$2" + for phase in "${phaseList[@]}"; do + if [[ ${phases[$phase]:-unset} == unset ]]; then + abort "Unknown phase: $phase" + fi + phases[$phase]=1 + done + shift + ;; + --stop-after-disko) + echo "WARNING: --stop-after-disko is deprecated, use --phases kexec,disko instead" 2>&1 + phases[kexec]=1 + phases[disko]=1 + phases[install]=0 + phases[reboot]=0 + ;; + --no-reboot) + echo "WARNING: --no-reboot is deprecated, use --phases kexec,disko,install instead" 2>&1 + phases[kexec]=1 + phases[disko]=1 + phases[install]=1 + phases[reboot]=0 + ;; + --from) + nixCopyOptions+=("--from" "$2") + shift + ;; + --option) + key=$2 + shift + value=$2 + shift + nixOptions+=("--option" "$key" "$value") + ;; + --no-substitute-on-destination) + substituteOnDestination=n + ;; + --build-on-remote) + echo "WARNING: --build-on-remote is deprecated, use --build-on remote instead" 2>&1 + buildOnRemote=y + buildOn="remote" + ;; + --env-password) + envPassword=y + ;; + --vm-test) + vmTest=y + ;; + *) + if [[ -z ${sshConnection} ]]; then + sshConnection="$1" + else + showUsage + exit 1 + fi + ;; + esac + shift + done + + diskoAttr="${diskoMode}Script" + + if [[ ${diskoDeps} == "n" ]]; then + diskoAttr="${diskoAttr}NoDeps" + fi + + if [[ ${printBuildLogs} == "y" ]]; then + nixOptions+=("-L") + fi + + if [[ $substituteOnDestination == "y" ]]; then + nixCopyOptions+=("--substitute-on-destination") + fi + + if [[ $vmTest == "n" ]] && [[ -z ${sshConnection} ]]; then + abort "ssh-host must be set" + fi + + if [[ $buildOn == "local" ]] && [[ $buildOnRemote == "y" ]]; then + abort "Conflicting flags: --build-on local and --build-on-remote used." + fi + + if [[ -n ${flake} ]]; then + if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then + flake="${BASH_REMATCH[1]}" + flakeAttr="${BASH_REMATCH[2]}" + fi + if [[ -z ${flakeAttr} ]]; then + echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri." >&2 + echo 'For example, to use the output nixosConfigurations.foo from the flake.nix, append "#foo" to the flake-uri.' >&2 + exit 1 + fi + + # Support .#foo shorthand + if [[ $flakeAttr != nixosConfigurations.* ]]; then + flakeAttr="nixosConfigurations.\"$flakeAttr\".config" + fi + fi + +} + +# ssh wrapper +runSshNoTty() { + ssh -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@" +} +runSshTimeout() { + timeout 10 ssh -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@" +} +runSsh() { + ssh "$sshTtyParam" -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@" +} + +nixCopy() { + NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $sshKeyDir/nixos-anywhere ${sshArgs[*]}" nix copy \ + "${nixOptions[@]}" \ + "${nixCopyOptions[@]}" \ + "$@" +} +nixBuild() { + NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $sshKeyDir/nixos-anywhere ${sshArgs[*]}" nix build \ + --print-out-paths \ + --no-link \ + "${nixBuildFlags[@]}" \ + "${nixOptions[@]}" \ + "$@" +} + +runVmTest() { + if [[ -z ${flakeAttr} ]]; then + echo "--vm-test is not supported with --store-paths" >&2 + echo "Please use --flake instead or build config.system.build.installTest of your nixos configuration manually" >&2 + exit 1 + fi + + if [[ ${buildOn} == "remote" ]]; then + echo "--vm-test is not supported with --build-on-remote" >&2 + exit 1 + fi + if [[ -n ${extraFiles} ]]; then + echo "--vm-test is not supported with --extra-files" >&2 + exit 1 + fi + if [ ${#diskEncryptionKeys[@]} -gt 0 ]; then + echo "--vm-test is not supported with --disk-encryption-keys" >&2 + exit 1 + fi + nix build \ + --print-out-paths \ + --no-link \ + -L \ + "${nixBuildFlags[@]}" \ + "${nixOptions[@]}" \ + "${flake}#${flakeAttr}.system.build.installTest" +} + +uploadSshKey() { + # ssh-copy-id requires this directory + mkdir -p "$HOME/.ssh/" + if [[ -n ${sshPrivateKeyFile} ]]; then + cp "$sshPrivateKeyFile" "$sshKeyDir/nixos-anywhere" + ssh-keygen -y -f "$sshKeyDir/nixos-anywhere" >"$sshKeyDir/nixos-anywhere.pub" + else + # we generate a temporary ssh keypair that we can use during nixos-anywhere + ssh-keygen -t ed25519 -f "$sshKeyDir"/nixos-anywhere -P "" -C "nixos-anywhere" >/dev/null + fi + + declare -a sshCopyIdArgs + if [[ -n ${sshPrivateKeyFile} ]]; then + unset SSH_AUTH_SOCK # don't use system agent if key was supplied + sshCopyIdArgs+=(-o "IdentityFile=${sshPrivateKeyFile}" -f) + fi + + step Uploading install SSH keys + until + if [[ ${envPassword} == y ]]; then + sshpass -e \ + ssh-copy-id \ + -i "$sshKeyDir"/nixos-anywhere.pub \ + -o ConnectTimeout=10 \ + -o UserKnownHostsFile=/dev/null \ + -o IdentitiesOnly=yes \ + -o StrictHostKeyChecking=no \ + "${sshCopyIdArgs[@]}" \ + "${sshArgs[@]}" \ + "$sshConnection" + else + ssh-copy-id \ + -i "$sshKeyDir"/nixos-anywhere.pub \ + -o ConnectTimeout=10 \ + -o UserKnownHostsFile=/dev/null \ + -o StrictHostKeyChecking=no \ + "${sshCopyIdArgs[@]}" \ + "${sshArgs[@]}" \ + "$sshConnection" + fi + do + sleep 3 + done +} + +importFacts() { + step Gathering machine facts + local facts filteredFacts + if ! facts=$(runSsh -o ConnectTimeout=10 enableDebug=$enableDebug sh -- <"$here"/get-facts.sh); then + exit 1 + fi + filteredFacts=$(echo "$facts" | grep -E '^(has|is)[A-Za-z0-9_]+=\S+') + if [[ -z $filteredFacts ]]; then + abort "Retrieving host facts via ssh failed. Check with --debug for the root cause, unless you have done so already" + fi + # make facts available in script + # shellcheck disable=SC2046 + export $(echo "$filteredFacts" | xargs) + + for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid; do + if [[ -z ${!var} ]]; then + abort "Failed to retrieve fact $var from host" + fi + done +} + +checkBuildLocally() { + local system extraPlatforms machineSystem + system="$(nix --extra-experimental-features 'nix-command flakes' config show system)" + extraPlatforms="$(nix --extra-experimental-features 'nix-command flakes' config show extra-platforms)" + + if [[ $# -gt 0 ]]; then + machineSystem=$1 + elif [[ -n ${nixosSystem} ]]; then + machineSystem="$(cat "${nixosSystem}"/system)" + else + machineSystem="$(nix --extra-experimental-features 'nix-command flakes' eval --raw "${flake}"#"${flakeAttr}".pkgs.system 2>/dev/null || echo "unknown")" + if [[ ${machineSystem} == "unknown" ]]; then + buildOn=auto + return + fi + fi + + if [[ ${system} == "${machineSystem}" ]]; then + buildOn=local + return + fi + + if [[ ${extraPlatforms} == "*${machineSystem}*" ]]; then + buildOn=local + return + fi + + local entropy + entropy="$(date +'%Y%m%d%H%M%S')" + if nix build \ + -L \ + "${nixOptions[@]}" \ + --expr \ + "derivation { system = \"$system\"; name = \"env-$entropy\"; builder = \"/bin/sh\"; args = [ \"-c\" \"echo > \$out\" ]; }"; then + # The local build failed + buildOn=local + fi + + buildOn=remote +} + +generateHardwareConfig() { + local maybeSudo="$maybeSudo" + mkdir -p "$(dirname "$hardwareConfigPath")" + case "$hardwareConfigBackend" in + nixos-facter) + if [[ ${isInstaller} == "y" ]]; then + if [[ ${hasNixOSFacter} == "n" ]]; then + abort "nixos-facter is not available in booted installer, use nixos-generate-config. For nixos-facter, you may want to boot an installer image from here instead: https://github.com/nix-community/nixos-images" + fi + else + maybeSudo="" + fi + + step "Generating hardware-configuration.nix using nixos-facter" + runSshNoTty -o ConnectTimeout=10 ${maybeSudo} "nixos-facter" >"$hardwareConfigPath" + ;; + nixos-generate-config) + step "Generating hardware-configuration.nix using nixos-generate-config" + runSshNoTty -o ConnectTimeout=10 nixos-generate-config --show-hardware-config --no-filesystems >"$hardwareConfigPath" + ;; + *) + abort "Unknown hardware config backend: $hardwareConfigBackend" + ;; + esac + + # to make sure nix knows about the new file + if command -v git >/dev/null; then + # handle relative paths + hardwareConfigPath="$(realpath "$hardwareConfigPath")" + pushd "$(dirname "$hardwareConfigPath")" + if git rev-parse --is-inside-work-tree >/dev/null; then + git add --intent-to-add --force -- "$hardwareConfigPath" + fi + popd + fi +} + +runKexec() { + if [[ ${isKexec} == "y" ]] || [[ ${isInstaller} == "y" ]]; then + return + fi + + if [[ ${isContainer} != "none" ]]; then + echo "WARNING: This script does not support running from a '${isContainer}' container. kexec will likely not work" >&2 + fi + + if [[ $kexecUrl == "" ]]; then + case "${isArch}" in + x86_64 | aarch64) + kexecUrl="https://github.com/nix-community/nixos-images/releases/download/nixos-24.11/nixos-kexec-installer-noninteractive-${isArch}-linux.tar.gz" + ;; + *) + abort "Unsupported architecture: ${isArch}. Our default kexec images only support x86_64 and aarch64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information." + ;; + esac + fi + + step Switching system into kexec + runSsh sh <<SSH +set -efu ${enableDebug} +$maybeSudo rm -rf /root/kexec +$maybeSudo mkdir -p /root/kexec +SSH + + # no way to reach global ipv4 destinations, use gh-v6.com automatically if github url + if [[ ${hasIpv6Only} == "y" ]] && [[ $kexecUrl == "https://github.com/"* ]]; then + kexecUrl=${kexecUrl/"github.com"/"gh-v6.com"} + fi + + if [[ -f $kexecUrl ]]; then + runSsh "${maybeSudo} tar -C /root/kexec -xvzf-" <"$kexecUrl" + elif [[ ${hasCurl} == "y" ]]; then + runSsh "curl --fail -Ss -L '${kexecUrl}' | ${maybeSudo} tar -C /root/kexec -xvzf-" + elif [[ ${hasWget} == "y" ]]; then + runSsh "wget '${kexecUrl}' -O- | ${maybeSudo} tar -C /root/kexec -xvzf-" + else + curl --fail -Ss -L "${kexecUrl}" | runSsh "${maybeSudo} tar -C /root/kexec -xvzf-" + fi + + runSsh <<SSH +TMPDIR=/root/kexec setsid ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags "${kexecExtraFlags}" +SSH + + # use the default SSH port to connect at this point + for i in "${!sshArgs[@]}"; do + if [[ ${sshArgs[i]} == "-p" ]]; then + sshArgs[i + 1]=$postKexecSshPort + break + fi + done + + # wait for machine to become unreachable. + while runSshTimeout -- exit 0; do sleep 1; done + + # After kexec we explicitly set the user to root@ + sshConnection="root@${sshHost}" + + # waiting for machine to become available again + until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done +} + +runDisko() { + local diskoScript=$1 + for path in "${!diskEncryptionKeys[@]}"; do + step "Uploading ${diskEncryptionKeys[$path]} to $path" + runSsh "umask 077; mkdir -p \"$(dirname "$path")\"; cat > $path" <"${diskEncryptionKeys[$path]}" + done + if [[ -n ${diskoScript} ]]; then + nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "$diskoScript" + elif [[ ${buildOn} == "remote" ]]; then + step Building disko script + # We need to do a nix copy first because nix build doesn't have --no-check-sigs + # Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359 + nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "${flake}#${flakeAttr}.system.build.${diskoMode}Script" \ + --derivation --no-check-sigs + # If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store` + diskoScript=$( + nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}" \ + --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$sshKeyDir%2Fnixos-anywhere&$sshStoreSettings" + ) + fi + + step Formatting hard drive with disko + runSsh "$diskoScript" +} + +nixosInstall() { + local nixosSystem=$1 + if [[ -n ${nixosSystem} ]]; then + step Uploading the system closure + nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "$nixosSystem" + elif [[ ${buildOn} == "remote" ]]; then + step Building the system closure + # We need to do a nix copy first because nix build doesn't have --no-check-sigs + # Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359 + nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "${flake}#${flakeAttr}.system.build.toplevel" \ + --derivation --no-check-sigs + # If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store` + nixosSystem=$( + nixBuild "${flake}#${flakeAttr}.system.build.toplevel" \ + --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$sshKeyDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" + ) + fi + + if [[ -n ${extraFiles} ]]; then + step Copying extra files + tar -C "$extraFiles" -cpf- . | runSsh "tar -C /mnt -xf- --no-same-owner" + + runSsh "chmod 755 /mnt" # tar also changes permissions of /mnt + fi + + if [[ ${#extraFilesOwnership[@]} -gt 0 ]]; then + # shellcheck disable=SC2016 + printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do chown -R "$ownership" "/mnt/$file"; done' + fi + + step Installing NixOS + runSsh sh <<SSH +set -eu ${enableDebug} +# when running not in nixos we might miss this directory, but it's needed in the nixos chroot during installation +export PATH="\$PATH:/run/current-system/sw/bin" + +# needed for installation if initrd-secrets are used +mkdir -p /mnt/tmp +chmod 777 /mnt/tmp +if [ ${copyHostKeys-n} = "y" ]; then + # NB we copy host keys that are in turn copied by kexec installer. + mkdir -m 755 -p /mnt/etc/ssh + for p in /etc/ssh/ssh_host_*; do + # Skip if the source file does not exist (i.e. glob did not match any files) + # or the destination already exists (e.g. copied with --extra-files). + if [ ! -e "\$p" ] || [ -e "/mnt/\$p" ]; then + continue + fi + cp -a "\$p" "/mnt/\$p" + done +fi +# https://stackoverflow.com/a/13864829 +if [ ! -z ${NIXOS_NO_CHECK+0} ]; then + export NIXOS_NO_CHECK +fi +nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem" +if [[ ${phases[reboot]} == 1 ]]; then + if command -v zpool >/dev/null && [ "\$(zpool list)" != "no pools available" ]; then + # we always want to export the zfs pools so people can boot from it without force import + umount -Rv /mnt/ + swapoff -a + zpool export -a || true + fi + nohup sh -c 'sleep 6 && reboot' >/dev/null & +fi +SSH + +} + +main() { + parseArgs "$@" + + if [[ ${vmTest} == y ]]; then + if [[ ${hardwareConfigBackend} != "none" ]]; then + abort "--vm-test is not supported with --generate-hardware-config. You need to generate the hardware configuration before you can run the VM test." >&2 + fi + runVmTest + exit 0 + fi + + if [[ ${buildOn} == "auto" ]]; then + checkBuildLocally + fi + + # parse flake nixos-install style syntax, get the system attr + if [[ -n ${flake} ]]; then + if [[ ${buildOn} == "local" ]] && [[ ${hardwareConfigBackend} == "none" ]]; then + if [[ ${phases[disko]} == 1 ]]; then + diskoScript=$(nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}") + fi + if [[ ${phases[install]} == 1 ]]; then + nixosSystem=$(nixBuild "${flake}#${flakeAttr}.system.build.toplevel") + fi + fi + elif [[ -n ${diskoScript} ]] && [[ -n ${nixosSystem} ]]; then + if [[ ! -e ${diskoScript} ]] || [[ ! -e ${nixosSystem} ]]; then + abort "${diskoScript} and ${nixosSystem} must be existing store-paths" + fi + else + abort "--flake or --store-paths must be set" + fi + + if [[ -n ${SSH_PRIVATE_KEY} ]] && [[ -z ${sshPrivateKeyFile} ]]; then + # $sshKeyDir is getting deleted on trap EXIT + sshPrivateKeyFile="$sshKeyDir/from-env" + ( + umask 077 + printf '%s\n' "$SSH_PRIVATE_KEY" >"$sshPrivateKeyFile" + ) + fi + + sshSettings=$(ssh "${sshArgs[@]}" -G "${sshConnection}") + sshUser=$(echo "$sshSettings" | awk '/^user / { print $2 }') + sshHost=$(echo "$sshSettings" | awk '/^hostname / { print $2 }') + + uploadSshKey + + importFacts + + if [[ ${hasTar-n} == "n" ]]; then + abort "no tar command found, but required to unpack kexec tarball" + fi + + if [[ ${hasCpio-n} == "n" ]]; then + abort "no cpio command found, but required to build the new initrd" + fi + + if [[ ${hasSetsid-n} == "n" ]]; then + abort "no setsid command found, but required to run the kexec script under a new session" + fi + + maybeSudo="" + if [[ ${hasSudo-n} == "y" ]]; then + maybeSudo="sudo" + elif [[ ${hasDoas-n} == "y" ]]; then + maybeSudo="doas" + fi + + if [[ ${isOs} != "Linux" ]]; then + abort "This script requires Linux as the operating system, but got $isOs" + fi + + if [[ ${phases[kexec]} == 1 ]]; then + runKexec + fi + + if [[ ${hardwareConfigBackend} != "none" ]]; then + generateHardwareConfig + fi + + # Before we do not have a valid hardware configuration we don't know the machine system + if [[ ${buildOn} == "auto" ]]; then + local remoteSystem + remoteSystem=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features nix-command config show system) + checkBuildLocally "${remoteSystem}" + # if we cannot figure it out at this point, we will build on the remote host + if [[ ${buildOn} == "auto" ]]; then + buildOn=remote + fi + fi + + if [[ ${buildOn} != "remote" ]] && [[ -n ${flake} ]] && [[ -z ${diskoScript} ]]; then + if [[ ${phases[disko]} == 1 ]]; then + diskoScript=$(nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}") + fi + if [[ ${phases[install]} == 1 ]]; then + nixosSystem=$(nixBuild "${flake}#${flakeAttr}.system.build.toplevel") + fi + fi + + # Installation will fail if non-root user is used for installer. + # Switch to root user by copying authorized_keys. + if [[ ${isInstaller} == "y" ]] && [[ ${sshUser} != "root" ]]; then + # Allow copy to fail if authorized_keys does not exist, like if using /etc/ssh/authorized_keys.d/ + runSsh "${maybeSudo} mkdir -p /root/.ssh; ${maybeSudo} cp ~/.ssh/authorized_keys /root/.ssh || true" + sshConnection="root@${sshHost}" + fi + + if [[ ${phases[disko]} == 1 ]]; then + runDisko "$diskoScript" + fi + + if [[ ${phases[install]} == 1 ]]; then + nixosInstall "$nixosSystem" + fi + + if [[ ${phases[reboot]} == 1 ]]; then + step Waiting for the machine to become unreachable due to reboot + while runSshTimeout -- exit 0; do sleep 1; done + fi + + step "Done!" +} + +main "$@" diff --git a/launch/.terraform/modules/peertube.deploy/terraform/README.md b/launch/.terraform/modules/peertube.deploy/terraform/README.md new file mode 100644 index 0000000..2d66b1a --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/README.md @@ -0,0 +1,21 @@ +# NixOS-Anywhere Terraform Modules Overview + +The nixos-Anywhere terraform modules allow you to use Terraform for installing +and updating NixOS. It simplifies the deployment process by integrating +nixos-anywhere functionality. + +Here's a brief overview of each module: + +- **[All-in-One](all-in-one.md)**: This is a consolidated module that first + installs NixOS using nixos-anywhere and then keeps it updated with + nixos-rebuild. If you choose this, you won't need additional deployment tools + like colmena. +- **[Install](install.md)**: This module focuses solely on installing NixOS via + nixos-anywhere. +- **[NixOS-Rebuild](nixos-rebuild.md)**: Use this module to remotely update an + existing NixOS machine using nixos-rebuild. +- **[Nix-Build](nix-build.md)**: This is a handy helper module designed to build + a flake attribute or an attribute from a nix file. + +For detailed information and usage examples, click on the respective module +links above. diff --git a/launch/.terraform/modules/peertube.deploy/terraform/all-in-one.md b/launch/.terraform/modules/peertube.deploy/terraform/all-in-one.md new file mode 100644 index 0000000..c7279cf --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/all-in-one.md @@ -0,0 +1,235 @@ +# All-in-one + +Combines the install and nixos-rebuild module in one interface to install NixOS +with nixos-anywhere and then keep it up-to-date with nixos-rebuild. + +## Example + +```hcl +locals { + ipv4 = "192.0.2.1" +} + +module "deploy" { + source = "github.com/nix-community/nixos-anywhere//terraform/all-in-one" + # with flakes + nixos_system_attr = ".#nixosConfigurations.mymachine.config.system.build.toplevel" + nixos_partitioner_attr = ".#nixosConfigurations.mymachine.config.system.build.diskoScript" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #nixos_system_attr = "config.system.build.toplevel" + #nixos_partitioner_attr = "config.system.build.diskoScript" + + target_host = local.ipv4 + # when instance id changes, it will trigger a reinstall + instance_id = local.ipv4 + # useful if something goes wrong + # debug_logging = true + # build the closure on the remote machine instead of locally + # build_on_remote = true + # script is below + extra_files_script = "${path.module}/decrypt-ssh-secrets.sh" + disk_encryption_key_scripts = [{ + path = "/tmp/secret.key" + # script is below + script = "${path.module}/decrypt-zfs-key.sh" + }] + # Optional, arguments passed to special_args here will be available from a NixOS module in this example the `terraform` argument: + # { terraform, ... }: { + # networking.interfaces.enp0s3.ipv4.addresses = [{ address = terraform.ip; prefixLength = 24; }]; + # } + # Note that this will means that your NixOS configuration will always depend on terraform! + # Skip to `Pass data persistently to the NixOS` for an alternative approach + #special_args = { + # terraform = { + # ip = "192.0.2.0" + # } + #} +} +``` + +_Note:_ You need to mark scripts as executable (`chmod +x`) + +### ./decrypt-ssh-secrets.sh + +```bash +#!/usr/bin/env bash + +mkdir -p etc/ssh var/lib/secrets + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +umask 0177 +sops --extract '["initrd_ssh_key"]' --decrypt "$SCRIPT_DIR/secrets.yaml" >./var/lib/secrets/initrd_ssh_key + +# restore umask +umask 0022 + +for keyname in ssh_host_rsa_key ssh_host_rsa_key.pub ssh_host_ed25519_key ssh_host_ed25519_key.pub; do + if [[ $keyname == *.pub ]]; then + umask 0133 + else + umask 0177 + fi + sops --extract '["'$keyname'"]' --decrypt "$SCRIPT_DIR/secrets.yaml" >"./etc/ssh/$keyname" +done +``` + +### ./decrypt-zfs-key.sh + +```bash +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +cd "$SCRIPT_DIR" +sops --extract '["zfs-key"]' --decrypt "$SCRIPT_DIR/secrets.yaml" +``` + +## See also + +- [nixos-wiki setup](https://github.com/NixOS/nixos-wiki-infra/blob/main/terraform/nixos-wiki/main.tf) + for hetzner-cloud + +## Pass data persistently to the NixOS + +This guide outlines how to pass data from Terraform to NixOS by generating a +file during Terraform execution and including it in your NixOS configuration. +This approach works well if your Terraform and NixOS configurations are stored +in the same Git repository. + +### Why Use This Method? + +This method provides a straightforward way to transfer values from Terraform to +NixOS without relying on special_args. + +- **Advantages**: + - You can continue to use nix build or nixos-rebuild to evaluate your + configuration without interruption. Simplifies configuration management by + centralizing state in a single repository. +- **Disadvantages**: + - Deploying new machines requires tracking additional state. Every time + Terraform updates the JSON file, you'll need to commit these changes to your + repository. + +### Implementation + +Add the following snippet to your Terraform configuration to create and manage a +JSON file containing the necessary variables for NixOS. This file will be +automatically added to your Git repository, ensuring the data persists. + +Assuming you have your terraform and nixos configuration in the same git +repository. You can use the following snippet to `git add` a file generated by +`terraform` during execution to pass data from terraform to NixOS. These changes +should be committed afterwards. This is an alternative over using +`special_args`. Advantage: you can still use nix build or nixos-rebuild on your +flake to evaluate your configuration. Disadvantage: Deploying new machines also +means you need to track additional state and make additional commits whenever +terraform updates the json file. + +```hcl +locals { + nixos_vars_file = "nixos-vars.json" # Path to the JSON file containing NixOS variables + nixos_vars = { + ip = "192.0.2.0" # Replace with actual variables + } +} +resource "local_file" "nixos_vars" { + content = jsonencode(local.nixos_vars) # Converts variables to JSON + filename = local.nixos_vars_file # Specifies the output file path + file_permission = "600" + + # Automatically adds the generated file to Git + provisioner "local-exec" { + interpreter = ["bash", "-c"] + command = "git add -f '${local.nixos_vars_file}'" + } +} +``` + +After applying the Terraform changes, ensure you commit the updated +`nixos-vars.json` file to your Git repository: + +```bash +git commit -m "Update NixOS variables from Terraform" +``` + +You can import this json file into your configuration like this: + +```nix +let + nixosVars = builtins.fromJSON (builtins.readFile ./nixos-vars.json); +in +{ + # Example usage of imported variables + networking.hostName = "example-machine"; + networking.interfaces.eth0.ipv4.addresses = [ + { + address = nixosVars.ip; # Use the IP from nixos-vars.json + prefixLength = 24; + } + ]; +} +``` + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +| -------------------------------------------------------------------------------------- | ---------------- | ------- | +| <a name="module_install"></a> [install](#module_install) | ../install | n/a | +| <a name="module_nixos-rebuild"></a> [nixos-rebuild](#module_nixos-rebuild) | ../nixos-rebuild | n/a | +| <a name="module_partitioner-build"></a> [partitioner-build](#module_partitioner-build) | ../nix-build | n/a | +| <a name="module_system-build"></a> [system-build](#module_system-build) | ../nix-build | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | :------: | +| <a name="input_debug_logging"></a> [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no | +| <a name="input_deployment_ssh_key"></a> [deployment\_ssh\_key](#input_deployment_ssh_key) | Content of private key used to deploy to the target\_host after initial installation. To ensure maximum security, it is advisable to connect to your host using ssh-agent instead of relying on this variable | `string` | `null` | no | +| <a name="input_disk_encryption_key_scripts"></a> [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system | <pre>list(object({<br> path = string<br> script = string<br> }))</pre> | `[]` | no | +| <a name="input_extra_environment"></a> [extra\_environment](#input_extra_environment) | Extra environment variables to be set during installation. This can be useful to set extra variables for the extra\_files\_script or disk\_encryption\_key\_scripts | `map(string)` | `{}` | no | +| <a name="input_extra_files_script"></a> [extra\_files\_script](#input_extra_files_script) | A script that should place files in the current directory that will be copied to the targets / directory | `string` | `null` | no | +| <a name="input_file"></a> [file](#input_file) | Nix file containing the nixos\_system\_attr and nixos\_partitioner\_attr. Use this if you are not using flake | `string` | `null` | no | +| <a name="input_install_port"></a> [install\_port](#input_install_port) | SSH port used to connect to the target\_host, before installing NixOS. If null than the value of `target_port` is used | `string` | `null` | no | +| <a name="input_install_ssh_key"></a> [install\_ssh\_key](#input_install_ssh_key) | Content of private key used to connect to the target\_host during initial installation | `string` | `null` | no | +| <a name="input_install_user"></a> [install\_user](#input_install_user) | SSH user used to connect to the target\_host, before installing NixOS. If null than the value of `target_host` is used | `string` | `null` | no | +| <a name="input_instance_id"></a> [instance\_id](#input_instance_id) | The instance id of the target\_host, used to track when to reinstall the machine | `string` | `null` | no | +| <a name="input_kexec_tarball_url"></a> [kexec\_tarball\_url](#input_kexec_tarball_url) | NixOS kexec installer tarball url | `string` | `null` | no | +| <a name="input_nix_options"></a> [nix\_options](#input_nix_options) | the options of nix | `map(string)` | `{}` | no | +| <a name="input_nixos_facter_path"></a> [nixos\_facter\_path](#input_nixos_facter_path) | Path to which to write a `facter.json` generated by `nixos-facter`. | `string` | `""` | no | +| <a name="input_nixos_generate_config_path"></a> [nixos\_generate\_config\_path](#input_nixos_generate_config_path) | Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`. | `string` | `""` | no | +| <a name="input_nixos_partitioner_attr"></a> [nixos\_partitioner\_attr](#input_nixos_partitioner_attr) | Nixos partitioner and mount script i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.diskoNoDeps or just your-evaluated.config.system.build.diskNoDeps. `config.system.build.diskNoDeps` is provided by the disko nixos module | `string` | n/a | yes | +| <a name="input_nixos_system_attr"></a> [nixos\_system\_attr](#input_nixos_system_attr) | The nixos system to deploy i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.toplevel or just your-evaluated-nixos.config.system.build.toplevel if you are not using flakes | `string` | n/a | yes | +| <a name="input_no_reboot"></a> [no\_reboot](#input_no_reboot) | DEPRECATED: Use `phases` instead. Do not reboot after installation | `bool` | `false` | no | +| <a name="input_phases"></a> [phases](#input_phases) | Phases to run. See `nixos-anywhere --help` for more information | `set(string)` | <pre>[<br> "kexec",<br> "disko",<br> "install",<br> "reboot"<br>]</pre> | no | +| <a name="input_special_args"></a> [special\_args](#input_special_args) | A map exposed as NixOS's `specialArgs` thru a file. | `any` | `{}` | no | +| <a name="input_build_on_remote"></a> [build\_on\_remote](#input_build_on_remote) | Build the closure on the remote machine instead of building it locally and copying it over | `bool` | `false` | no | +| <a name="input_stop_after_disko"></a> [stop\_after\_disko](#input_stop_after_disko) | DEPRECATED: Use `phases` instead. Exit after disko formatting | `bool` | `false` | no | +| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | +| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host after installing NixOS. If install\_port is not set than this port is also used before installing. | `number` | `22` | no | +| <a name="input_target_user"></a> [target\_user](#input_target_user) | SSH user used to connect to the target\_host after installing NixOS. If install\_user is not set than this user is also used before installing. | `string` | `"root"` | no | + +## Outputs + +| Name | Description | +| ----------------------------------------------------- | ----------- | +| <a name="output_result"></a> [result](#output_result) | n/a | + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/peertube.deploy/terraform/all-in-one/main.tf b/launch/.terraform/modules/peertube.deploy/terraform/all-in-one/main.tf new file mode 100644 index 0000000..fa5d7eb --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/all-in-one/main.tf @@ -0,0 +1,64 @@ +module "system-build" { + source = "../nix-build" + attribute = var.nixos_system_attr + file = var.file + nix_options = var.nix_options + special_args = var.special_args +} + +module "partitioner-build" { + source = "../nix-build" + attribute = var.nixos_partitioner_attr + file = var.file + nix_options = var.nix_options + special_args = var.special_args +} + +locals { + install_user = var.install_user == null ? var.target_user : var.install_user + install_port = var.install_port == null ? var.target_port : var.install_port +} + +module "install" { + source = "../install" + kexec_tarball_url = var.kexec_tarball_url + target_user = local.install_user + target_host = var.target_host + target_port = local.install_port + nixos_partitioner = module.partitioner-build.result.out + nixos_system = module.system-build.result.out + ssh_private_key = var.install_ssh_key + debug_logging = var.debug_logging + extra_files_script = var.extra_files_script + disk_encryption_key_scripts = var.disk_encryption_key_scripts + extra_environment = var.extra_environment + instance_id = var.instance_id + phases = var.phases + nixos_generate_config_path = var.nixos_generate_config_path + nixos_facter_path = var.nixos_facter_path + build_on_remote = var.build_on_remote + # deprecated attributes + stop_after_disko = var.stop_after_disko + no_reboot = var.no_reboot +} + +module "nixos-rebuild" { + depends_on = [ + module.install + ] + + # Do not execute this step if var.stop_after_disko == true + count = var.stop_after_disko ? 0 : 1 + + source = "../nixos-rebuild" + nixos_system = module.system-build.result.out + ssh_private_key = var.deployment_ssh_key + target_host = var.target_host + target_user = var.target_user + target_port = var.target_port + install_bootloader = var.install_bootloader +} + +output "result" { + value = module.system-build.result +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/all-in-one/variables.tf b/launch/.terraform/modules/peertube.deploy/terraform/all-in-one/variables.tf new file mode 100644 index 0000000..3ca2792 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/all-in-one/variables.tf @@ -0,0 +1,151 @@ +variable "kexec_tarball_url" { + type = string + description = "NixOS kexec installer tarball url" + default = null +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_partitioner_attr" { + type = string + description = "Nixos partitioner and mount script i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.diskoNoDeps or just your-evaluated.config.system.build.diskNoDeps. `config.system.build.diskNoDeps` is provided by the disko nixos module" +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_system_attr" { + type = string + description = "The nixos system to deploy i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.toplevel or just your-evaluated-nixos.config.system.build.toplevel if you are not using flakes" +} + +variable "file" { + type = string + description = "Nix file containing the nixos_system_attr and nixos_partitioner_attr. Use this if you are not using flake" + default = null +} + +variable "target_host" { + type = string + description = "DNS host to deploy to" +} + +variable "install_user" { + type = string + description = "SSH user used to connect to the target_host, before installing NixOS. If null than the value of `target_host` is used" + default = null +} + +variable "install_port" { + type = string + description = "SSH port used to connect to the target_host, before installing NixOS. If null than the value of `target_port` is used" + default = null +} + +variable "target_user" { + type = string + description = "SSH user used to connect to the target_host after installing NixOS. If install_user is not set than this user is also used before installing." + default = "root" +} + +variable "target_port" { + type = number + description = "SSH port used to connect to the target_host after installing NixOS. If install_port is not set than this port is also used before installing." + default = 22 +} + +variable "instance_id" { + type = string + description = "The instance id of the target_host, used to track when to reinstall the machine" + default = null +} + +variable "install_ssh_key" { + type = string + description = "Content of private key used to connect to the target_host during initial installation" + default = null +} + +variable "deployment_ssh_key" { + type = string + description = "Content of private key used to deploy to the target_host after initial installation. To ensure maximum security, it is advisable to connect to your host using ssh-agent instead of relying on this variable" + default = null +} + +variable "debug_logging" { + type = bool + description = "Enable debug logging" + default = false +} + +variable "stop_after_disko" { + type = bool + description = "DEPRECATED: Use `phases` instead. Exit after disko formatting" + default = false +} + +variable "no_reboot" { + type = bool + description = "DEPRECATED: Use `phases` instead. Do not reboot after installation" + default = false +} + +variable "phases" { + type = set(string) + description = "Phases to run. See `nixos-anywhere --help` for more information" + default = ["kexec", "disko", "install", "reboot"] +} + +variable "extra_files_script" { + type = string + description = "A script that should place files in the current directory that will be copied to the targets / directory" + default = null +} + +variable "disk_encryption_key_scripts" { + type = list(object({ + path = string + script = string + })) + description = "Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system" + default = [] +} + +variable "extra_environment" { + type = map(string) + description = "Extra environment variables to be set during installation. This can be useful to set extra variables for the extra_files_script or disk_encryption_key_scripts" + default = {} +} + +variable "nix_options" { + type = map(string) + description = "the options of nix" + default = {} +} + +variable "nixos_generate_config_path" { + type = string + description = "Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`." + default = "" +} + +variable "nixos_facter_path" { + type = string + description = "Path to which to write a `facter.json` generated by `nixos-facter`." + default = "" +} + +variable "special_args" { + type = any + default = {} + description = "A map exposed as NixOS's `specialArgs` thru a file." +} + +variable "build_on_remote" { + type = bool + description = "Build the closure on the remote machine instead of building it locally and copying it over" + default = false +} + +variable "install_bootloader" { + type = bool + description = "Install/re-install the bootloader" + default = false +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/install.md b/launch/.terraform/modules/peertube.deploy/terraform/install.md new file mode 100644 index 0000000..eb3439f --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/install.md @@ -0,0 +1,91 @@ +# Install + +Install NixOS with nixos-anywhere + +## Example + +```hcl +locals { + ipv4 = "192.0.2.1" +} + +module "system-build" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + # with flakes + attribute = ".#nixosConfigurations.mymachine.config.system.build.toplevel" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #attribute = "config.system.build.toplevel" +} + +module "disko" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + # with flakes + attribute = ".#nixosConfigurations.mymachine.config.system.build.diskoScript" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #attribute = "config.system.build.diskoScript" +} + +module "install" { + source = "github.com/nix-community/nixos-anywhere//terraform/install" + nixos_system = module.system-build.result.out + nixos_partitioner = module.disko.result.out + target_host = local.ipv4 +} +``` + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +| --------------------------------------------------- | ------- | +| <a name="provider_null"></a> [null](#provider_null) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ------------------------------------------------------------------------------------------------------------------- | -------- | +| [null_resource.nixos-remote](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | :------: | +| <a name="input_build_on_remote"></a> [build\_on\_remote](#input_build_on_remote) | Build the closure on the remote machine instead of building it locally and copying it over | `bool` | `false` | no | +| <a name="input_debug_logging"></a> [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no | +| <a name="input_disk_encryption_key_scripts"></a> [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system | <pre>list(object({<br> path = string<br> script = string<br> }))</pre> | `[]` | no | +| <a name="input_extra_environment"></a> [extra\_environment](#input_extra_environment) | Extra environment variables to be set during installation. This can be useful to set extra variables for the extra\_files\_script or disk\_encryption\_key\_scripts | `map(string)` | `{}` | no | +| <a name="input_extra_files_script"></a> [extra\_files\_script](#input_extra_files_script) | A script that should place files in the current directory that will be copied to the targets / directory | `string` | `null` | no | +| <a name="input_flake"></a> [flake](#input_flake) | The flake to install the system from | `string` | `""` | no | +| <a name="input_instance_id"></a> [instance\_id](#input_instance_id) | The instance id of the target\_host, used to track when to reinstall the machine | `string` | `null` | no | +| <a name="input_kexec_tarball_url"></a> [kexec\_tarball\_url](#input_kexec_tarball_url) | NixOS kexec installer tarball url | `string` | `null` | no | +| <a name="input_nixos_facter_path"></a> [nixos\_facter\_path](#input_nixos_facter_path) | Path to which to write a `facter.json` generated by `nixos-facter`. This option cannot be set at the same time as `nixos_generate_config_path`. | `string` | `""` | no | +| <a name="input_nixos_generate_config_path"></a> [nixos\_generate\_config\_path](#input_nixos_generate_config_path) | Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`. This option cannot be set at the same time as `nixos_facter_path`. | `string` | `""` | no | +| <a name="input_nixos_partitioner"></a> [nixos\_partitioner](#input_nixos_partitioner) | nixos partitioner and mount script | `string` | `""` | no | +| <a name="input_nixos_system"></a> [nixos\_system](#input_nixos_system) | The nixos system to deploy | `string` | `""` | no | +| <a name="input_no_reboot"></a> [no\_reboot](#input_no_reboot) | DEPRECATED: Use `phases` instead. Do not reboot after installation | `bool` | `false` | no | +| <a name="input_phases"></a> [phases](#input_phases) | Phases to run. See `nixos-anywhere --help` for more information | `list(string)` | <pre>[<br> "kexec",<br> "disko",<br> "install",<br> "reboot"<br>]</pre> | no | +| <a name="input_ssh_private_key"></a> [ssh\_private\_key](#input_ssh_private_key) | Content of private key used to connect to the target\_host | `string` | `""` | no | +| <a name="input_stop_after_disko"></a> [stop\_after\_disko](#input_stop_after_disko) | DEPRECATED: Use `phases` instead. Exit after disko formatting | `bool` | `false` | no | +| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | +| <a name="input_target_pass"></a> [target\_pass](#input_target_pass) | Password used to connect to the target\_host | `string` | `null` | no | +| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host | `number` | `22` | no | +| <a name="input_target_user"></a> [target\_user](#input_target_user) | SSH user used to connect to the target\_host | `string` | `"root"` | no | + +## Outputs + +No outputs. + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/peertube.deploy/terraform/install/main.tf b/launch/.terraform/modules/peertube.deploy/terraform/install/main.tf new file mode 100644 index 0000000..9f1816a --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/install/main.tf @@ -0,0 +1,35 @@ +locals { + disk_encryption_key_scripts = [for k in var.disk_encryption_key_scripts : "\"${k.path}\" \"${k.script}\""] + removed_phases = setunion(var.stop_after_disko ? ["install"] : [], (var.no_reboot ? ["reboot"] : [])) + phases = setsubtract(var.phases, local.removed_phases) + arguments = jsonencode({ + ssh_private_key = var.ssh_private_key + debug_logging = var.debug_logging + kexec_tarball_url = var.kexec_tarball_url + nixos_partitioner = var.nixos_partitioner + nixos_system = var.nixos_system + target_user = var.target_user + target_host = var.target_host + target_port = var.target_port + target_pass = var.target_pass + extra_files_script = var.extra_files_script + build_on_remote = var.build_on_remote + flake = var.flake + phases = join(",", local.phases) + nixos_generate_config_path = var.nixos_generate_config_path + nixos_facter_path = var.nixos_facter_path + }) +} + +resource "null_resource" "nixos-remote" { + triggers = { + instance_id = var.instance_id + } + provisioner "local-exec" { + environment = merge({ + ARGUMENTS = local.arguments + }, var.extra_environment) + command = "${path.module}/run-nixos-anywhere.sh ${join(" ", local.disk_encryption_key_scripts)}" + quiet = var.debug_logging + } +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/install/providers.tf b/launch/.terraform/modules/peertube.deploy/terraform/install/providers.tf new file mode 100644 index 0000000..c3e00a5 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/install/providers.tf @@ -0,0 +1,5 @@ +terraform { + required_providers { + null = { source = "hashicorp/null" } + } +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/install/run-nixos-anywhere.sh b/launch/.terraform/modules/peertube.deploy/terraform/install/run-nixos-anywhere.sh new file mode 100755 index 0000000..1d259a1 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/install/run-nixos-anywhere.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +declare -A input + +while IFS= read -r -d '' key && IFS= read -r -d '' value; do + input[$key]=$value +done < <(jq -j 'to_entries[] | (.key, "\u0000", .value, "\u0000")' <<<"${ARGUMENTS}") + +args=() + +if [[ ${input[debug_logging]} == "true" ]]; then + set -x + declare -p input + args+=("--debug") +fi +if [[ ${input[kexec_tarball_url]} != "null" ]]; then + args+=("--kexec" "${input[kexec_tarball_url]}") +fi +if [[ ${input[build_on_remote]} == "true" ]]; then + args+=("--build-on-remote") +fi +if [[ -n ${input[flake]} ]]; then + args+=("--flake" "${input[flake]}") +else + args+=("--store-paths" "${input[nixos_partitioner]}" "${input[nixos_system]}") +fi +if [[ -n ${input[nixos_generate_config_path]} ]]; then + if [[ -n ${input[nixos_facter_path]} ]]; then + echo "cannot set both variables 'nixos_generate_config_path' and 'nixos_facter_path'!" >&2 + exit 1 + fi + args+=("--generate-hardware-config" "nixos-generate-config" "${input[nixos_generate_config_path]}") +elif [[ -n ${input[nixos_facter_path]} ]]; then + args+=("--generate-hardware-config" "nixos-facter" "${input[nixos_facter_path]}") +fi +args+=(--phases "${input[phases]}") +if [[ ${input[ssh_private_key]} != null ]]; then + export SSH_PRIVATE_KEY="${input[ssh_private_key]}" +fi +if [[ ${input[target_pass]} != null ]]; then + export SSHPASS=${input[target_pass]} + args+=("--env-password") +fi + +tmpdir=$(mktemp -d) +cleanup() { + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +if [[ ${input[extra_files_script]} != "null" ]]; then + if [[ ! -f ${input[extra_files_script]} ]]; then + echo "extra_files_script '${input[extra_files_script]}' does not exist" + exit 1 + fi + if [[ ! -x ${input[extra_files_script]} ]]; then + echo "extra_files_script '${input[extra_files_script]}' is not executable" + exit 1 + fi + extra_files_script=$(realpath "${input[extra_files_script]}") + mkdir "${tmpdir}/extra-files" + pushd "${tmpdir}/extra-files" + $extra_files_script + popd + args+=("--extra-files" "${tmpdir}/extra-files") +fi + +args+=("-p" "${input[target_port]}") +args+=("${input[target_user]}@${input[target_host]}") + +keyIdx=0 +while [[ $# -gt 0 ]]; do + if [[ ! -f $2 ]]; then + echo "Script file '$2' does not exist" + exit 1 + fi + if [[ ! -x $2 ]]; then + echo "Script file '$2' is not executable" + exit 1 + fi + mkdir -p "${tmpdir}/keys" + "$2" >"${tmpdir}/keys/$keyIdx" + args+=("--disk-encryption-keys" "$1" "${tmpdir}/keys/$keyIdx") + shift + shift + keyIdx=$((keyIdx + 1)) +done + +nix run --extra-experimental-features 'nix-command flakes' "path:${SCRIPT_DIR}/../..#nixos-anywhere" -- "${args[@]}" diff --git a/launch/.terraform/modules/peertube.deploy/terraform/install/variables.tf b/launch/.terraform/modules/peertube.deploy/terraform/install/variables.tf new file mode 100644 index 0000000..cd07bf7 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/install/variables.tf @@ -0,0 +1,123 @@ +variable "kexec_tarball_url" { + type = string + description = "NixOS kexec installer tarball url" + default = null +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_partitioner" { + type = string + description = "nixos partitioner and mount script" + default = "" +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_system" { + type = string + description = "The nixos system to deploy" + default = "" +} + +variable "target_host" { + type = string + description = "DNS host to deploy to" +} + +variable "target_user" { + type = string + description = "SSH user used to connect to the target_host" + default = "root" +} + +variable "target_port" { + type = number + description = "SSH port used to connect to the target_host" + default = 22 +} + +variable "target_pass" { + type = string + description = "Password used to connect to the target_host" + default = null +} + +variable "ssh_private_key" { + type = string + description = "Content of private key used to connect to the target_host" + default = "" +} + +variable "instance_id" { + type = string + description = "The instance id of the target_host, used to track when to reinstall the machine" + default = null +} + +variable "debug_logging" { + type = bool + description = "Enable debug logging" + default = false +} + +variable "extra_files_script" { + type = string + description = "A script that should place files in the current directory that will be copied to the targets / directory" + default = null +} + +variable "disk_encryption_key_scripts" { + type = list(object({ + path = string + script = string + })) + description = "Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system" + default = [] +} + +variable "extra_environment" { + type = map(string) + description = "Extra environment variables to be set during installation. This can be useful to set extra variables for the extra_files_script or disk_encryption_key_scripts" + default = {} +} + +variable "stop_after_disko" { + type = bool + description = "DEPRECATED: Use `phases` instead. Exit after disko formatting" + default = false +} + +variable "no_reboot" { + type = bool + description = "DEPRECATED: Use `phases` instead. Do not reboot after installation" + default = false +} + +variable "phases" { + type = list(string) + description = "Phases to run. See `nixos-anywhere --help` for more information" + default = ["kexec", "disko", "install", "reboot"] +} + +variable "build_on_remote" { + type = bool + description = "Build the closure on the remote machine instead of building it locally and copying it over" + default = false +} + +variable "flake" { + type = string + description = "The flake to install the system from" + default = "" +} + +variable "nixos_generate_config_path" { + type = string + description = "Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`. This option cannot be set at the same time as `nixos_facter_path`." + default = "" +} + +variable "nixos_facter_path" { + type = string + description = "Path to which to write a `facter.json` generated by `nixos-facter`. This option cannot be set at the same time as `nixos_generate_config_path`." + default = "" +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nix-build.md b/launch/.terraform/modules/peertube.deploy/terraform/nix-build.md new file mode 100644 index 0000000..fe63af1 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nix-build.md @@ -0,0 +1,47 @@ +# Nix-build + +Small helper module to run do build a flake attribute or attribute from a nix +file. + +## Example + +- See [install](install.md) or [nixos-rebuild](nixos-rebuild.md) + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +| --------------------------------------------------------------- | ------- | +| <a name="provider_external"></a> [external](#provider_external) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| --------------------------------------------------------------------------------------------------------------------------- | ----------- | +| [external_external.nix-build](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---------------------------------------------------------------------- | --------------------------------------------------- | ------------- | ------- | :------: | +| <a name="input_attribute"></a> [attribute](#input_attribute) | the attribute to build, can also be a flake | `string` | n/a | yes | +| <a name="input_file"></a> [file](#input_file) | the nix file to evaluate, if not run in flake mode | `string` | `null` | no | +| <a name="input_nix_options"></a> [nix\_options](#input_nix_options) | the options of nix | `map(string)` | `{}` | no | +| <a name="input_special_args"></a> [special\_args](#input_special_args) | A map exposed as NixOS's `specialArgs` thru a file. | `any` | `{}` | no | + +## Outputs + +| Name | Description | +| ----------------------------------------------------- | ----------- | +| <a name="output_result"></a> [result](#output_result) | n/a | + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nix-build/main.tf b/launch/.terraform/modules/peertube.deploy/terraform/nix-build/main.tf new file mode 100644 index 0000000..7a3c335 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nix-build/main.tf @@ -0,0 +1,17 @@ +locals { + nix_options = jsonencode({ + options = { for k, v in var.nix_options : k => v } + }) +} +data "external" "nix-build" { + program = [ "${path.module}/nix-build.sh" ] + query = { + attribute = var.attribute + file = var.file + nix_options = local.nix_options + special_args = jsonencode(var.special_args) + } +} +output "result" { + value = data.external.nix-build.result +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nix-build/nix-build.sh b/launch/.terraform/modules/peertube.deploy/terraform/nix-build/nix-build.sh new file mode 100755 index 0000000..0e22357 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nix-build/nix-build.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -efu + +declare file attribute nix_options special_args +eval "$(jq -r '@sh "attribute=\(.attribute) file=\(.file) nix_options=\(.nix_options) special_args=\(.special_args)"')" +if [ "${nix_options}" != '{"options":{}}' ]; then + options=$(echo "${nix_options}" | jq -r '.options | to_entries | map("--option \(.key) \(.value)") | join(" ")') +else + options="" +fi +if [[ ${special_args-} == "{}" ]]; then + # no special arguments, proceed as normal + if [[ -n ${file-} ]] && [[ -e ${file-} ]]; then + # shellcheck disable=SC2086 + out=$(nix build --no-link --json $options -f "$file" "$attribute") + else + # shellcheck disable=SC2086 + out=$(nix build --no-link --json ${options} "$attribute") + fi +else + if [[ ${file-} != 'null' ]]; then + echo "special_args are currently only supported when using flakes!" >&2 + exit 1 + fi + # pass the args in a pure fashion by extending the original config + rest="$(echo "${attribute}" | cut -d "#" -f 2)" + # e.g. config_path=nixosConfigurations.aarch64-linux.myconfig + config_path="${rest%.config.*}" + # e.g. config_attribute=config.system.build.toplevel + config_attribute="config.${rest#*.config.}" + + # grab flake nar from error message + flake_rel="$(echo "${attribute}" | cut -d "#" -f 1)" + # e.g. flake_rel="." + flake_dir="$(readlink -f "${flake_rel}")" + flake_path="${flake_dir}/flake.nix" + flake_json="$(nix flake prefetch "${flake_dir}" --json)" + flake_nar="$(echo "$flake_json" | jq -r '.hash')" + store_path="$(echo "${flake_json}" | jq -r '.storePath')" + # while we have a store path now, for a repo this reflects its root level, + # so search for the largest child segment yielding a match in that store dir. + iter_path="${flake_path}" + while [[ ${iter_path} != "/" ]]; do + parent="$(dirname "${iter_path}")" + child_segment="${flake_path//$parent/}" + if [[ -f "${store_path}${child_segment}" ]]; then + target_segment="${child_segment}" + fi + iter_path="${parent}" + done + # substitute variables into the template + nix_expr="(builtins.getFlake ''file://${flake_dir}?dir=${target_segment//\/flake.nix/}&narHash=${flake_nar}'').${config_path}.extendModules { specialArgs = builtins.fromJSON ''${special_args}''; }" + # inject `special_args` into nixos config's `specialArgs` + # shellcheck disable=SC2086 + out=$(nix build --no-link --json ${options} --expr "${nix_expr}" "${config_attribute}") +fi +printf '%s' "$out" | jq -c '.[].outputs' diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nix-build/variables.tf b/launch/.terraform/modules/peertube.deploy/terraform/nix-build/variables.tf new file mode 100644 index 0000000..bad23f7 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nix-build/variables.tf @@ -0,0 +1,22 @@ +variable "attribute" { + type = string + description = "the attribute to build, can also be a flake" +} + +variable "file" { + type = string + description = "the nix file to evaluate, if not run in flake mode" + default = null +} + +variable "nix_options" { + type = map(string) + description = "the options of nix" + default = {} +} + +variable "special_args" { + type = any + default = {} + description = "A map exposed as NixOS's `specialArgs` thru a file." +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild.md b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild.md new file mode 100644 index 0000000..0be26bb --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild.md @@ -0,0 +1,66 @@ +# Nixos-rebuild + +Update NixOS machine with nixos-rebuild on a remote machine + +## Example + +```hcl +locals { + ipv4 = "192.0.2.1" +} + +module "system-build" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + # with flakes + attribute = ".#nixosConfigurations.mymachine.config.system.build.toplevel" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #attribute = "config.system.build.toplevel" +} + +module "deploy" { + source = "github.com/nix-community/nixos-anywhere//terraform/nixos-rebuild" + nixos_system = module.system-build.result.out + target_host = local.ipv4 +} +``` + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +| --------------------------------------------------- | ------- | +| <a name="provider_null"></a> [null](#provider_null) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| -------------------------------------------------------------------------------------------------------------------- | -------- | +| [null_resource.nixos-rebuild](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | -------- | :------: | +| <a name="input_ignore_systemd_errors"></a> [ignore\_systemd\_errors](#input_ignore_systemd_errors) | Ignore systemd errors happening during deploy | `bool` | `false` | no | +| <a name="input_nixos_system"></a> [nixos\_system](#input_nixos_system) | The nixos system to deploy | `string` | n/a | yes | +| <a name="input_ssh_private_key"></a> [ssh\_private\_key](#input_ssh_private_key) | Content of private key used to connect to the target\_host. If set to - no key is passed to openssh and ssh will use its own configuration | `string` | `"-"` | no | +| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | +| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host | `number` | `22` | no | +| <a name="input_target_user"></a> [target\_user](#input_target_user) | User to deploy as | `string` | `"root"` | no | + +## Outputs + +No outputs. + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/deploy.sh b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/deploy.sh new file mode 100755 index 0000000..61da6e1 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/deploy.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +set -uex -o pipefail + +if [ "$#" -ne 6 ]; then + echo "USAGE: $0 NIXOS_SYSTEM TARGET_USER TARGET_HOST TARGET_PORT IGNORE_SYSTEMD_ERRORS INSTALL_BOOTLOADER" >&2 + exit 1 +fi + +NIXOS_SYSTEM=$1 +TARGET_USER=$2 +TARGET_HOST=$3 +TARGET_PORT=$4 +IGNORE_SYSTEMD_ERRORS=$5 +INSTALL_BOOTLOADER=$6 + +shift 6 + +TARGET="${TARGET_USER}@${TARGET_HOST}" + +workDir=$(mktemp -d) +trap 'rm -rf "$workDir"' EXIT + +sshOpts=(-p "${TARGET_PORT}") +sshOpts+=(-o UserKnownHostsFile=/dev/null) +sshOpts+=(-o StrictHostKeyChecking=no) + +set +x +if [[ -n ${SSH_KEY+x} && ${SSH_KEY} != "-" ]]; then + sshPrivateKeyFile="$workDir/ssh_key" + # Create the file with 0700 - umask calculation: 777 - 700 = 077 + ( + umask 077 + echo "$SSH_KEY" >"$sshPrivateKeyFile" + ) + unset SSH_AUTH_SOCK # don't use system agent if key was supplied + sshOpts+=(-o "IdentityFile=${sshPrivateKeyFile}") +fi +set -x + +try=1 +until NIX_SSHOPTS="${sshOpts[*]}" nix copy -s --experimental-features nix-command --to "ssh://$TARGET" "$NIXOS_SYSTEM"; do + if [[ $try -gt 10 ]]; then + echo "retries exhausted" >&2 + exit 1 + fi + sleep 10 + try=$((try + 1)) +done + +if [[ $INSTALL_BOOTLOADER == "true" ]]; then + extra_env='NIXOS_INSTALL_BOOTLOADER=1' +else + extra_env='' +fi + +switchCommand="nix-env -p /nix/var/nix/profiles/system --set $(printf "%q" "$NIXOS_SYSTEM"); $extra_env /nix/var/nix/profiles/system/bin/switch-to-configuration switch" + +if [[ $TARGET_USER != "root" ]]; then + switchCommand="sudo bash -c '$switchCommand'" +fi +deploy_status=0 +# shellcheck disable=SC2029 +ssh "${sshOpts[@]}" "$TARGET" "$switchCommand" || deploy_status="$?" +if [[ $IGNORE_SYSTEMD_ERRORS == "true" && $deploy_status == "4" ]]; then + exit 0 +fi +exit "$deploy_status" diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/main.tf b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/main.tf new file mode 100644 index 0000000..84461c8 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/main.tf @@ -0,0 +1,11 @@ +resource "null_resource" "nixos-rebuild" { + triggers = { + store_path = var.nixos_system + } + provisioner "local-exec" { + environment = { + SSH_KEY = var.ssh_private_key + } + command = "${path.module}/deploy.sh ${var.nixos_system} ${var.target_user} ${var.target_host} ${var.target_port} ${var.ignore_systemd_errors} ${var.install_bootloader}" + } +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/variables.tf b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/variables.tf new file mode 100644 index 0000000..90e0b0c --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/nixos-rebuild/variables.tf @@ -0,0 +1,39 @@ +variable "nixos_system" { + type = string + description = "The nixos system to deploy" +} + +variable "target_host" { + type = string + description = "DNS host to deploy to" +} + +variable "target_user" { + type = string + default = "root" + description = "User to deploy as" +} + +variable "target_port" { + type = number + description = "SSH port used to connect to the target_host" + default = 22 +} + +variable "ssh_private_key" { + type = string + description = "Content of private key used to connect to the target_host. If set to - no key is passed to openssh and ssh will use its own configuration" + default = "-" +} + +variable "ignore_systemd_errors" { + type = bool + description = "Ignore systemd errors happening during deploy" + default = false +} + +variable "install_bootloader" { + type = bool + description = "Install/re-install the bootloader" + default = false +} diff --git a/launch/.terraform/modules/peertube.deploy/terraform/update-docs.sh b/launch/.terraform/modules/peertube.deploy/terraform/update-docs.sh new file mode 100755 index 0000000..b67d98a --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/terraform/update-docs.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" +files=() +find "${SCRIPT_DIR}"/* -type d | while read -r i; do + module_name=$(basename "$i") + markdown_file="${SCRIPT_DIR}/${module_name}.md" + terraform-docs --config "${SCRIPT_DIR}/.terraform-docs.yml" markdown table --output-file "${markdown_file}" --output-mode inject "${module_name}" + files+=("${markdown_file}") +done +cd .. +nix fmt -- --no-cache diff --git a/launch/.terraform/modules/peertube.deploy/tests/flake-module.nix b/launch/.terraform/modules/peertube.deploy/tests/flake-module.nix new file mode 100644 index 0000000..784175f --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/flake-module.nix @@ -0,0 +1,33 @@ +{ withSystem, inputs, ... }: + +{ + flake.checks.x86_64-linux = withSystem "x86_64-linux" ( + { + pkgs, + system, + inputs', + config, + ... + }: + let + testInputsUnstable = { + inherit pkgs; + inherit (inputs.disko.nixosModules) disko; + nixos-anywhere = config.packages.nixos-anywhere; + kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-unstable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz"; + }; + testInputsStable = testInputsUnstable // { + kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-stable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz"; + }; + in + { + from-nixos = import ./from-nixos.nix testInputsUnstable; + from-nixos-stable = import ./from-nixos.nix testInputsStable; + from-nixos-with-sudo = import ./from-nixos-with-sudo.nix testInputsUnstable; + from-nixos-with-sudo-stable = import ./from-nixos-with-sudo.nix testInputsStable; + from-nixos-with-generated-config = import ./from-nixos-generate-config.nix testInputsUnstable; + from-nixos-build-on-remote = import ./from-nixos-build-on-remote.nix testInputsUnstable; + from-nixos-separated-phases = import ./from-nixos-separated-phases.nix testInputsUnstable; + } + ); +} diff --git a/launch/.terraform/modules/peertube.deploy/tests/from-nixos-build-on-remote.nix b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-build-on-remote.nix new file mode 100644 index 0000000..5261713 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-build-on-remote.nix @@ -0,0 +1,56 @@ +(import ./lib/test-base.nix) ( + { pkgs, ... }: + { + name = "from-nixos-build-on-remote"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + }; + }; + testScript = '' + def create_test_machine( + oldmachine=None, **kwargs + ): # taken from <nixpkgs/nixos/tests/installer.nix> + start_command = [ + "${pkgs.qemu_test}/bin/qemu-kvm", + "-cpu", + "max", + "-m", + "1024", + "-virtfs", + "local,path=/nix/store,security_model=none,mount_tag=nix-store", + "-drive", + f"file={oldmachine.state_dir}/installed.qcow2,id=drive1,if=none,index=1,werror=report", + "-device", + "virtio-blk-pci,drive=drive1", + ] + machine = create_machine(start_command=" ".join(start_command), **kwargs) + driver.machines.append(machine) + return machine + start_all() + + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --build-on-remote \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + root@installed >&2 + """) + try: + installed.shutdown() + except BrokenPipeError: + # qemu has already exited + pass + new_machine = create_test_machine(oldmachine=installed, name="after_install") + new_machine.start() + hostname = new_machine.succeed("hostname").strip() + assert "nixos-anywhere" == hostname, f"'nixos-anywhere' != '{hostname}'" + ''; + } +) diff --git a/launch/.terraform/modules/peertube.deploy/tests/from-nixos-generate-config.nix b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-generate-config.nix new file mode 100644 index 0000000..5b4d65d --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-generate-config.nix @@ -0,0 +1,71 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-generate-config"; + nodes = { + installer = + { pkgs, ... }: + { + imports = [ + ./modules/installer.nix + ]; + environment.systemPackages = [ pkgs.jq ]; + }; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + }; + }; + testScript = '' + start_all() + installer.fail("test -f /tmp/hw/config.nix") + installer.succeed("echo super-secret > /tmp/disk-1.key") + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ + --phases kexec,disko \ + --generate-hardware-config nixos-generate-config /tmp/hw/config.nix \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + root@installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-1.key)'" + echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-2.key)'" + """) + + installer.succeed("cat /tmp/hw/config.nix >&2") + installer.succeed("nix-instantiate --parse /tmp/hw/config.nix") + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "disk-2.key: 'another-secret'" in output, f"output does not contain expected values: {output}" + + installer.fail("test -f /test/hw/config.json") + + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ + --phases kexec,disko \ + --generate-hardware-config nixos-facter /tmp/hw/config.json \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-1.key)'" + echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-2.key)'" + """) + + installer.succeed("cat /tmp/hw/config.json >&2") + installer.succeed("jq < /tmp/hw/config.json") + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "disk-2.key: 'another-secret'" in output, f"output does not contain expected values: {output}" + ''; +} diff --git a/launch/.terraform/modules/peertube.deploy/tests/from-nixos-separated-phases.nix b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-separated-phases.nix new file mode 100644 index 0000000..2b267b6 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-separated-phases.nix @@ -0,0 +1,52 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-separated-phases"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.nixos = { + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + extraGroups = [ "wheel" ]; + }; + security.sudo.enable = true; + security.sudo.wheelNeedsPassword = false; + }; + }; + testScript = '' + start_all() + + with subtest("Kexec Phase"): + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --phases kexec \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + nixos@installed >&2 + """) + + with subtest("Disko Phase"): + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --phases disko \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + installed >&2 + """) + + with subtest("Install Phase"): + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --phases install \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + root@installed >&2 + """) + ''; +} diff --git a/launch/.terraform/modules/peertube.deploy/tests/from-nixos-with-sudo.nix b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-with-sudo.nix new file mode 100644 index 0000000..839dec3 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/from-nixos-with-sudo.nix @@ -0,0 +1,40 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-with-sudo"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.nixos = { + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + extraGroups = [ "wheel" ]; + }; + security.sudo.enable = true; + security.sudo.wheelNeedsPassword = false; + }; + }; + testScript = '' + start_all() + installer.succeed("echo super-secret > /tmp/disk-1.key") + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --phases kexec,disko \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + nixos@installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-1.key)'" + echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-2.key)'" + """) + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "disk-2.key: 'another-secret'" in output, f"output does not contain expected values: {output}" + ''; +} diff --git a/launch/.terraform/modules/peertube.deploy/tests/from-nixos.nix b/launch/.terraform/modules/peertube.deploy/tests/from-nixos.nix new file mode 100644 index 0000000..0f3d134 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/from-nixos.nix @@ -0,0 +1,76 @@ +(import ./lib/test-base.nix) ( + { pkgs, ... }: + { + name = "from-nixos"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + }; + }; + testScript = '' + def create_test_machine( + oldmachine=None, **kwargs + ): # taken from <nixpkgs/nixos/tests/installer.nix> + start_command = [ + "${pkgs.qemu_test}/bin/qemu-kvm", + "-cpu", + "max", + "-m", + "1024", + "-virtfs", + "local,path=/nix/store,security_model=none,mount_tag=nix-store", + "-drive", + f"file={oldmachine.state_dir}/installed.qcow2,id=drive1,if=none,index=1,werror=report", + "-device", + "virtio-blk-pci,drive=drive1", + ] + machine = create_machine(start_command=" ".join(start_command), **kwargs) + driver.machines.append(machine) + return machine + start_all() + installer.succeed("mkdir -p /tmp/extra-files/var/lib/secrets") + installer.succeed("echo value > /tmp/extra-files/var/lib/secrets/key") + installer.succeed("mkdir -p /tmp/extra-files/home/user/.ssh") + installer.succeed("echo secretkey > /tmp/extra-files/home/user/.ssh/id_ed25519") + installer.succeed("echo publickey > /tmp/extra-files/home/user/.ssh/id_ed25519.pub") + installer.succeed("chmod 600 /tmp/extra-files/home/user/.ssh/id_ed25519") + ssh_key_path = "/etc/ssh/ssh_host_ed25519_key.pub" + ssh_key_output = installer.wait_until_succeeds(f""" + ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat {ssh_key_path} + """) + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --extra-files /tmp/extra-files \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + --chown /home/user 1000:100 \ + --copy-host-keys \ + root@installed >&2 + """) + try: + installed.shutdown() + except BrokenPipeError: + # qemu has already exited + pass + new_machine = create_test_machine(oldmachine=installed, name="after_install") + new_machine.start() + hostname = new_machine.succeed("hostname").strip() + assert "nixos-anywhere" == hostname, f"'nixos-anywhere' != '{hostname}'" + content = new_machine.succeed("cat /var/lib/secrets/key").strip() + assert "value" == content, f"secret does not have expected value: {content}" + ssh_key_content = new_machine.succeed(f"cat {ssh_key_path}").strip() + assert ssh_key_content in ssh_key_output, "SSH host identity changed" + priv_key_perms = new_machine.succeed("stat -c %a /home/user/.ssh/id_ed25519").strip() + assert priv_key_perms == "600", f"unexpected permissions for private key: {priv_key_perms}" + user_dir_ownership = new_machine.succeed("stat -c %u:%g /home/user").strip() + assert user_dir_ownership == "1000:100", f"unexpected user home dir permissions: {user_dir_ownership}" + ''; + } +) diff --git a/launch/.terraform/modules/peertube.deploy/tests/lib/test-base.nix b/launch/.terraform/modules/peertube.deploy/tests/lib/test-base.nix new file mode 100644 index 0000000..169d1fd --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/lib/test-base.nix @@ -0,0 +1,20 @@ +test: +{ + pkgs ? import <nixpkgs> { }, + nixos-anywhere ? pkgs.callPackage ../../src { }, + disko ? "${builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz"}/module.nix", + kexec-installer ? builtins.fetchurl "https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-noninteractive-${pkgs.stdenv.hostPlatform.system}.tar.gz", + ... +}: +let + inherit (pkgs) lib; + nixos-lib = import (pkgs.path + "/nixos/lib") { }; +in +(nixos-lib.runTest { + hostPkgs = pkgs; + # speed-up evaluation + defaults.documentation.enable = lib.mkDefault false; + # to accept external dependencies such as disko + node.specialArgs.inputs = { inherit nixos-anywhere disko kexec-installer; }; + imports = [ test ]; +}).config.result diff --git a/launch/.terraform/modules/peertube.deploy/tests/modules/installer.nix b/launch/.terraform/modules/peertube.deploy/tests/modules/installer.nix new file mode 100644 index 0000000..e5c22d4 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/modules/installer.nix @@ -0,0 +1,22 @@ +{ pkgs, inputs, ... }: +let + disko = inputs.disko; + kexec-installer = inputs.kexec-installer; + system-to-install = pkgs.nixos [ + ./system-to-install.nix + disko + ]; +in +{ + system.activationScripts.rsa-key = '' + ${pkgs.coreutils}/bin/install -D -m600 ${./ssh-keys/ssh} /root/.ssh/install_key + ''; + + environment.systemPackages = [ inputs.nixos-anywhere ]; + + environment.etc = { + "nixos-anywhere/disko".source = system-to-install.config.system.build.diskoScriptNoDeps; + "nixos-anywhere/system-to-install".source = system-to-install.config.system.build.toplevel; + "nixos-anywhere/kexec-installer".source = kexec-installer; + }; +} diff --git a/launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh b/launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh new file mode 100644 index 0000000..db6049c --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQAAAJDpULAq6VCw +KgAAAAtzc2gtZWQyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQ +AAAECpjfl5WMMIDvEyZJTeXzRNFzpDpj4fqdIXHZauKAlE5MziQ+DhXsMxhx64DxUhR0G/ +DfSAz2pqAREDy/VUYEEFAAAACWxhc3NAbW9ycwECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh.pub b/launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh.pub new file mode 100644 index 0000000..e77d393 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/modules/ssh-keys/ssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMziQ+DhXsMxhx64DxUhR0G/DfSAz2pqAREDy/VUYEEF diff --git a/launch/.terraform/modules/peertube.deploy/tests/modules/system-to-install.nix b/launch/.terraform/modules/peertube.deploy/tests/modules/system-to-install.nix new file mode 100644 index 0000000..1181c94 --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/tests/modules/system-to-install.nix @@ -0,0 +1,47 @@ +{ modulesPath, self, ... }: +{ + imports = [ + (modulesPath + "/testing/test-instrumentation.nix") + (modulesPath + "/profiles/qemu-guest.nix") + (modulesPath + "/profiles/minimal.nix") + ]; + networking.hostName = "nixos-anywhere"; + documentation.enable = false; + hardware.enableAllFirmware = false; + networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs + boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms + disko.devices = { + disk = { + vda = { + device = "/dev/vda"; + type = "disk"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1M"; + type = "EF02"; + }; + ESP = { + size = "100M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/launch/.terraform/modules/peertube.deploy/treefmt/flake-module.nix b/launch/.terraform/modules/peertube.deploy/treefmt/flake-module.nix new file mode 100644 index 0000000..3e92a8e --- /dev/null +++ b/launch/.terraform/modules/peertube.deploy/treefmt/flake-module.nix @@ -0,0 +1,23 @@ +{ inputs, ... }: +{ + imports = [ + inputs.treefmt-nix.flakeModule + ]; + perSystem = + { config, pkgs, ... }: + { + treefmt = { + projectRootFile = "flake.nix"; + programs.mdsh.enable = true; + programs.nixpkgs-fmt.enable = true; + programs.shellcheck.enable = true; + programs.shfmt.enable = true; + programs.deno.enable = !pkgs.deno.meta.broken; + settings.formatter.shellcheck.options = [ + "-s" + "bash" + ]; + }; + formatter = config.treefmt.build.wrapper; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/CONTRIBUTING.md b/launch/.terraform/modules/pixelfed.deploy/CONTRIBUTING.md new file mode 100644 index 0000000..3aeefa7 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/CONTRIBUTING.md @@ -0,0 +1,23 @@ +To run `nixos-anywhere` from the repo: + +```console +nix run . -- --help +``` + +To format the code: + +```console +nix fmt +``` + +To run all tests: + +```console +nix flake check -vL +``` + +To run an individual test: + +``` +nix build .#checks.x86_64-linux.from-nixos -vL +``` diff --git a/launch/.terraform/modules/pixelfed.deploy/LICENSE b/launch/.terraform/modules/pixelfed.deploy/LICENSE new file mode 100644 index 0000000..1996195 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Numtide + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/launch/.terraform/modules/pixelfed.deploy/README.md b/launch/.terraform/modules/pixelfed.deploy/README.md new file mode 100644 index 0000000..cdf28cc --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/README.md @@ -0,0 +1,123 @@ +# nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" width="150" height="150"> + +[Documentation Index](docs/INDEX.md) + +## README + +Setting up a new machine is time-consuming, and becomes complicated when it +needs to be done remotely. If you're installing NixOS, the **nixos-anywhere** +tool allows you to pre-configure the whole process including: + +- Disk partitioning and formatting +- Configuring and installing NixOS +- Installing additional files and software + +You can then initiate an unattended installation with a single CLI command. +Since **nixos-anywhere** can access the new machine using SSH, it's ideal for +remote installations. + +Once you have initiated the command, there is no need to 'babysit' the +installation. It all happens automatically. + +You can use the stored configuration to repeat the same installation if you need +to. + +## Overview + +If you have machines on a mix of platforms, you'll need a common installation +solution that works anywhere. **nixos-anywhere** is ideal in this situation. + +**nixos-anywhere** can be used equally well for cloud servers, bare metal +servers such as Hetzner, and local servers accessible via a LAN. You can create +standard configurations, and use the same configuration to create identical +servers anywhere. + +You first create Nix configurations to specify partitioning, formatting and +NixOS configurations. Further options can be controlled by a flake and by +run-time switches. + +Once the configuration has been created, a single command will: + +- Connect to the remote server via SSH +- Detect whether a NixOS installer is present; if not, it will use the Linux + `kexec` tool to boot into a Nixos installer. +- Use the [disko](https://github.com/nix-community/disko) tool to partition and + format the hard drive +- Install NixOS +- Optionally install any Nix packages and other software required. +- Optionally copy additional files to the new machine + +It's also possible to use **nixos-anywhere** to simplify the installation on a +machine that has no current operating system, first booting from a NixOS +installer image. This feature is described in the +[how-to guide](./docs/howtos/no-os.md#installing-on-a-machine-with-no-operating-system). +It's useful because you can pre-configure your required software and +preferences, and build the new machine with a single command. + +**Important Note:** Never use a production server as the target. It will be +completely overwritten and all data lost. This tool should only be used for +commissioning a new computer or repurposing an old machine once all important +data has been migrated. + +## Prerequisites + +- Source Machine: + + - Can be any machine with Nix installed, e.g. a NixOS machine. + +- Target Machine: + + - Unless you're using the option to boot from a NixOS installer image, or + providing your own `kexec` image, it must be running x86-64 Linux with kexec + support. Most `x86_64` Linux systems do have kexec support. By providing + your own [image](./docs/howtos/custom-kexec.md#using-your-own-kexec-image) + you can also perform kexec for other architectures eg aarch64 + - The machine must be reachable over the public internet or local network. + Nixos-anywhere does not support wifi networks. If a VPN is needed, define a + custom installer via the --kexec flag which connects to your VPN. + - When `kexec` is used the target must have at least 1 GB of RAM, excluding + swap. + +## How to use nixos-anywhere + +The [Quickstart Guide](./docs/quickstart.md) gives more information on how to +run **nixos-anywhere** in its simplest form. For more specific instructions to +suit individual requirements, see the [How To Guide](./docs/howtos/INDEX.md). + +## Related Tools + +**nixos-anywhere** makes use of the +[disko](https://github.com/nix-community/disko) tool to handle the partitioning +and formatting of the disks. + +## Contact + +For questions, come join us in the +[nixos-anywhere](https://matrix.to/#/#nixos-anywhere:nixos.org) matrix room. + +## Licensing and Contribution details + +This software is provided free under the +[MIT License](https://opensource.org/licenses/MIT). + +--- + +This project is supported by [Numtide](https://numtide.com/). + + +We are a team of independent freelancers that love open source. We help our +customers make their project lifecycles more efficient by: + +- Providing and supporting useful tools such as this one +- Building and deploying infrastructure, and offering dedicated DevOps support +- Building their in-house Nix skills, and integrating Nix with their workflows +- Developing additional features and tools +- Carrying out custom research and development. + +[Contact us](https://numtide.com/contact) if you have a project in mind, or if +you need help with any of our supported tools, including this one. We'd love to +hear from you. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/INDEX.md b/launch/.terraform/modules/pixelfed.deploy/docs/INDEX.md new file mode 100644 index 0000000..67d4875 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/INDEX.md @@ -0,0 +1,11 @@ +# Table of Content: - nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="149"> + +- [README](../README.md) +- [Quickstart](./quickstart.md) +- [System Requirements](./requirements.md) +- [How to Guide](./howtos/INDEX.md) +- [Reference](./reference.md) diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/SUMMARY.md b/launch/.terraform/modules/pixelfed.deploy/docs/SUMMARY.md new file mode 100644 index 0000000..3bfaea5 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/SUMMARY.md @@ -0,0 +1,26 @@ +# Summary: - nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="149"> + +The **nixos-anywhere** tool allows you to pre-configure the whole process of +installing NixOS, and run the install remotely with a single CLI command. + +Refer to the following documentation for more information. + +[System Requirements](./requirements.md): CPU and memory requirements + +[Quickstart](./quickstart.md): Instructions for a typical installation + +[How to Guide](./howtos/INDEX.md): Instructions for non-typical use cases + +- [Installing on a machine with no operating system](./howtos/no-os.md) +- [Using your own kexec image](./howtos/custom-kexec.md) +- [Secrets and full disk encryption](./howtos/secrets.md) +- [Use without flakes](./howtos/use-without-flakes.md) +- [Terraform](./howtos/terraform.md) +- [Nix-channels / `NIX_PATH`](./howtos/nix-path.md) +- [IPv6-only targets](./howtos/ipv6.md) + +[Reference](./reference.md): Reference Guide diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/book.toml b/launch/.terraform/modules/pixelfed.deploy/docs/book.toml new file mode 100644 index 0000000..d053de4 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = [ ] +language = "en" +multilingual = false +src = "." +title = "nixos-anywhere - install NixOS everywhere" diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/cli.md b/launch/.terraform/modules/pixelfed.deploy/docs/cli.md new file mode 100644 index 0000000..d257755 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/cli.md @@ -0,0 +1,63 @@ +# CLI + +``` +Usage: nixos-anywhere [options] [<ssh-host>] + +Options: + +* -f, --flake <flake_uri> + set the flake to install the system from. +* --target-host <ssh-host> + specified the SSH target host to deploy onto. +* -i <identity_file> + selects which SSH private key file to use. +* -p, --ssh-port <ssh_port> + set the ssh port to connect with +* --ssh-option <ssh_option> + set an ssh option +* -L, --print-build-logs + print full build logs +* --env-password + set a password used by ssh-copy-id, the password should be set by + the environment variable SSHPASS +* -s, --store-paths <disko-script> <nixos-system> + set the store paths to the disko-script and nixos-system directly + if this is given, flake is not needed +* --no-reboot + do not reboot after installation, allowing further customization of the target installation. +* --kexec <path> + use another kexec tarball to bootstrap NixOS +* --kexec-extra-flags + extra flags to add into the call to kexec, e.g. "--no-sync" +* --ssh-store-setting <key> <value> + ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded. +* --post-kexec-ssh-port <ssh_port> + after kexec is executed, use a custom ssh port to connect. Defaults to 22 +* --copy-host-keys + copy over existing /etc/ssh/ssh_host_* host keys to the installation +* --stop-after-disko + exit after disko formatting, you can then proceed to install manually or some other way +* --extra-files <path> + contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten + Copied files will be owned by root. See documentation for details. +* --disk-encryption-keys <remote_path> <local_path> + copy the contents of the file or pipe in local_path to remote_path in the installer environment, + after kexec but before installation. Can be repeated. +* --no-substitute-on-destination + disable passing --substitute-on-destination to nix-copy +* --debug + enable debug output +* --option <key> <value> + nix option to pass to every nix related command +* --from <store-uri> + URL of the source Nix store to copy the nixos and disko closure from +* --build-on-remote + build the closure on the remote machine instead of locally and copy-closuring it +* --vm-test + build the system and test the disk configuration inside a VM without installing it to the target. +* --build-on auto|remote|local + sets the build on settings to auto, remote or local. Default is auto. + auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build + local: will build on the local host + remote: will build on the remote host +``` diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/flake-module.nix b/launch/.terraform/modules/pixelfed.deploy/docs/flake-module.nix new file mode 100644 index 0000000..2afe9d5 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/flake-module.nix @@ -0,0 +1,21 @@ +{ + perSystem = + { pkgs, lib, ... }: + { + packages.docs = + pkgs.runCommand "nixos-anywhere-docs" + { + passthru.serve = pkgs.writeShellScriptBin "serve" '' + set -euo pipefail + cd docs + workdir=$(${pkgs.coreutils}/bin/mktemp -d) + trap 'rm -rf "$workdir"' EXIT + ${pkgs.mdbook}/bin/mdbook serve --dest-dir "$workdir" + ''; + } + '' + cp -r ${lib.cleanSource ./.}/* . + ${pkgs.mdbook}/bin/mdbook build --dest-dir "$out" + ''; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos.md new file mode 100644 index 0000000..bfa55ce --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos.md @@ -0,0 +1 @@ +# How to Guide diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/INDEX.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/INDEX.md new file mode 100644 index 0000000..2d35663 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/INDEX.md @@ -0,0 +1,29 @@ +# How To Guide: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="129"> + +[Documentation Index](./INDEX.md) + +## Contents + +[Installing on a machine with no operating system](./no-os.md) + +[Kexec on systems with limited RAM](./limited-ram.md) + +[Copying files to the new installation](./extra-files.md) + +[Using your own kexec image](./custom-kexec.md) + +[Repair installations without wiping data](./disko-modes.md) + +[Secrets and full disk encryption](./secrets.md) + +[Use without flakes](./use-without-flakes.md) + +[Terraform](./terraform.md) + +[Nix-channels / `NIX_PATH`](./nix-path.md) + +[IPv6-only targets](./ipv6.md) diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/custom-kexec.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/custom-kexec.md new file mode 100644 index 0000000..5935a5f --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/custom-kexec.md @@ -0,0 +1,40 @@ +# Using your own kexec image + +By default, `nixos-anywhere` downloads the kexec image from the +[NixOS images repository](https://github.com/nix-community/nixos-images#kexec-tarballs). + +However, you can provide your own `kexec` image file if you need to use a +different one. This is particularly useful for architectures other than `x86_64` +and `aarch64`, since they don't have a pre-build image. + +To do this, use the `--kexec` command line switch followed by the path to your +image file. The image will be uploaded prior to execution. + +Here's an example command that demonstrates how to use a custom kexec image with +`nixos-anywhere`: + +``` +nix run github:nix-community/nixos-anywhere -- \ + --kexec "$(nix build --print-out-paths github:nix-community/nixos-images#packages.aarch64-linux.kexec-installer-nixos-unstable-noninteractive)/nixos-kexec-installer-noninteractive-aarch64-linux.tar.gz" \ + --flake 'github:your-user/your-repo#your-system' \ + root@yourip +``` + +Make sure to replace `github:your-user/your-repo#your-system` with the +appropriate Flake URL representing your NixOS configuration. + +The example above assumes that your local machine can build for aarch64 in one +of the following ways: + +- Natively + +- Through a remote builder + +- By emulating the architecture with qemu using the following NixOS + configuration: + +```nix +{ + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; +} +``` diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/disko-modes.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/disko-modes.md new file mode 100644 index 0000000..501f93f --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/disko-modes.md @@ -0,0 +1,19 @@ +# Repair installations without wiping data + +By default, nixos-anywhere will reformat all configured disks before running the +installation. However it is also possible to mount the filesystems of an +existing installation and run `nixos-install`. This is useful to recover from a +misconfigured NixOS installation by first booting into a NixOS installer or +recovery system. + +To only mount existing filesystems, add `--disko-mode mount` to +`nixos-anywhere`: + +``` +nix run github:nix-community/nixos-anywhere -- --disko-mode mount --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +1. This will first boot into a nixos-installer +2. Mounts disks with disko +3. Runs nixos-install based on the provided flake +4. Reboots the machine. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/extra-files.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/extra-files.md new file mode 100644 index 0000000..ca067d4 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/extra-files.md @@ -0,0 +1,114 @@ +# Copying files to the new installation + +The `--extra-files <path>` option allows copying files to the target host after +installation. + +The contents of the `<path>` is recursively copied and overwrites the targets +root (/). The contents _must_ be in a structure and permissioned as it should be +on the target. + +In this way, there is no need to repeatedly pass arguments (eg: a fictional +argument: `--copy <source> <dest>`) to `nixos-anywhere` to complete the intended +outcome. + +The path and directory structure passed to `--extra-files` should be prepared +beforehand. + +This allows a simple programmatic invocation of `nixos-anywhere` for multiple +hosts. + +## Simple Example + +You want `/etc/ssh/ssh_host_*` and `/persist` from the local system on the +target. The `<path>` contents will look like this: + +```console +$ cd /tmp +$ root=$(mktemp -d) +$ sudo cp --verbose --archive --parents /etc/ssh/ssh_host_* ${root} +$ cp --verbose --archive --link /persist ${root} +``` + +The directory structure would look like this: + +```console +drwx------ myuser1 users 20 tmp.d6nx5QUwPN +drwxr-xr-x root root 6 ├── etc +drwx------ myuser1 users 160 │ └── ssh +.rw------- root root 399 │ ├── ssh_host_ed25519_key +.rw-r--r-- root root 91 │ ├── ssh_host_ed25519_key.pub +drwxr-xr-x myuser1 users 22 └── persist +drwxr-xr-x myuser1 users 14 ├── all +drwxr-xr-x myuser1 users 22 │ ├── my +.rw-r--r-- myuser1 users 6 │ │ ├── test3 +drwxr-xr-x myuser1 users 10 │ │ └── things +.rw-r--r-- myuser1 users 6 │ │ └── test4 +.rw-r--r-- myuser1 users 6 │ └── test2 +drwxr-xr-x myuser1 users 0 ├── blah +.rw-r--r-- myuser1 users 6 └── test +``` + +**NOTE**: Permissions will be copied, but ownership on the target will be root. + +Then pass $root like: + +> nixos-anywhere --flake ".#" --extra-files $root --target-host root@newhost + +## Programmatic Example + +```sh +for host in host1 host2 host3; do + root="target/${host}" + install -d -m755 ${root}/etc/ssh + ssh-keygen -A -C root@${host} -f ${root} + nixos-anywhere --extra-files "${root}" --flake ".#${host}" --target-host "root@${host}" +done +``` + +## Considerations + +### Ownership + +The new system may have differing UNIX user and group id's for users created +during installation. + +When the files are extracted on the remote the copied data will be owned by +root. + +If you wish to change the ownership after the files are copied onto the system, +you can use the `--chown` option. + +For example, if you did `--chown /home/myuser/.ssh 1000:100`, this would equate +to running `chown -R /home/myuser/.ssh 1000:100` where the uid is 1000 and the +gid is 100. **Only do this when you can _guarantee_ what the uid and gid will +be.** + +### Symbolic Links + +Do not create symbolic links to reference data to copy. + +GNU `tar` is used to do the copy over ssh. It is an archival tool used to +re/store directory structures as is. Thus `tar` copies symbolic links created +with `ln -s` by default. It does not follow them to copy the underlying file. + +### Hard links + +**NOTE**: hard links can only be created on the same filesystem. + +If you have larger persistent data to copy to the target. GNU `tar` will copy +data referenced by hard links created with `ln`. A hard link does not create +another copy the data. + +To copy a directory tree to the new target you can use the `cp` command with the +`--link` option which creates hard links. + +#### Example + +```sh +cd /tmp +root=$(mktemp -d) +cp --verbose --archive --link --parents /persist/home/myuser ${root} +``` + +`--parents` will create the directory structure of the source at the +destination. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/ipv6.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/ipv6.md new file mode 100644 index 0000000..64eb7a6 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/ipv6.md @@ -0,0 +1,23 @@ +# NixOS-anywhere on IPv6-only targets + +As GitHub engineers still haven't enabled the IPv6 switch, the kexec image +hosted on GitHub, cannot be used unfortunately on IPv6-only hosts. However it is +possible to use an IPv6 proxy for GitHub content like that: + +``` +nixos-anywhere \ + --kexec https://gh-v6.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-noninteractive-x86_64-linux.tar.gz \ +... +``` + +This proxy is hosted by [numtide](https://numtide.com/). It also works for IPv4. + +Alternatively it is also possible to reference a local file: + +``` +nixos-anywhere \ + --kexec ./nixos-kexec-installer-noninteractive-x86_64-linux.tar.gz \ +... +``` + +This tarball will be then uploaded via sftp to the target. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/limited-ram.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/limited-ram.md new file mode 100644 index 0000000..0cbac65 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/limited-ram.md @@ -0,0 +1,29 @@ +# Kexec on Systems with Limited RAM + +When working with nixos-anywhere on systems with limited RAM (around 1GB), you +can use the `--no-disko-deps` option to reduce memory usage during installation. + +## How it works + +The `--no-disko-deps` option uploads only the disko partitioning script without +including its dependencies. This significantly reduces memory usage because: + +1. The installer normally stores all dependencies in memory +2. Partitioning tools can be quite large when bundled with their dependencies + +## Usage example + +```bash +nix run github:nix-community/nixos-anywhere -- --no-disko-deps --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +## Trade-off + +While this approach saves memory, it means the partitioning tools will be +whatever versions are available on the target system, rather than the specific +versions defined in your NixOS configuration. This could potentially lead to +version inconsistencies between the partitioning tools and the NixOS system +being installed. + +This trade-off is usually acceptable for memory-constrained environments where +installation would otherwise fail due to insufficient RAM. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/nix-path.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/nix-path.md new file mode 100644 index 0000000..1505af9 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/nix-path.md @@ -0,0 +1,57 @@ +# Nix-channels / `NIX_PATH` + +nixos-anywhere does not install channels onto the new system by default to save +time and disk space. This for example results in errors like: + +``` +(stack trace truncated; use '--show-trace' to show the full trace) + +error: file 'nixpkgs' was not found in the Nix search path (add it using $NIX_PATH or -I) + +at «none»:0: (source not available) +``` + +when using tools like nix-shell/nix-env that rely on `NIX_PATH` being set. + +# Solution 1: Set the `NIX_PATH` via nixos configuration (recommended) + +Instead of stateful channels, one can also populate the `NIX_PATH` using nixos +configuration instead: + +```nix +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + # ... other inputs + + outputs = inputs@{ nixpkgs, ... }: + { + nixosConfigurations.yoursystem = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; # adapt to your actual system + modules = [ + # This line will populate NIX_PATH + { nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ]; } + # ... other modules and your configuration.nix + ]; + }; + }; +} +``` + +Advantage: This solution will be automatically kept up-to-date every time the +flake is updated. + +In your shell you will see something in your `$NIX_PATH`: + +```shellSession +$ echo $NIX_PATH +/root/.nix-defexpr/channels:nixpkgs=/nix/store/8b61j28rpy11dg8hanbs2x710d8w3v0d-source +``` + +# Solution 2: Manually add the channel + +On the installed machine, run: + +```shellSession +$ nix-channel --add https://nixos.org/channels/nixos-unstable nixos +$ nix-channel --update +``` diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/no-os.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/no-os.md new file mode 100644 index 0000000..1c07cb0 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/no-os.md @@ -0,0 +1,71 @@ +# Installing on a machine with no operating system + +If your machine doesn't currently have an operating system installed, you can +still run `nixos-anywhere` remotely to automate the install. To do this, you +would first need to boot the target machine from the standard NixOS installer. +You can either boot from a USB or use `netboot`. + +The +[NixOS installation guide](https://nixos.org/manual/nixos/stable/index.html#sec-booting-from-usb) +has detailed instructions on how to boot the installer. + +When you run `nixos-anywhere`, it will determine whether a NixOS installer is +present by checking whether the `/etc/os-release` file contains the identifier +`VARIANT_ID=installer`. This identifier is available on releases NixOS 23.05 or +later. + +If an installer is detected, `nixos-anywhere` will not attempt to `kexec` into +its own image. This is particularly useful for targets that don't have enough +RAM for `kexec` or don't support `kexec`. + +NixOS starts an SSH server on the installer by default, but you need to set a +password in order to access it. To set a password for the `nixos` user, run the +following command in a terminal on the NixOS machine: + +``` +passwd +``` + +If you don't know the IP address of the installer on your network, you can find +it by running the following command: + +``` +$ ip addr +1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 + link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff + altname enp0s3 + altname ens3 + inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0 + valid_lft 86385sec preferred_lft 75585sec + inet6 fec0::5054:ff:fe12:3456/64 scope site dynamic mngtmpaddr noprefixroute + valid_lft 86385sec preferred_lft 14385sec + inet6 fe80::5054:ff:fe12:3456/64 scope link + valid_lft forever preferred_lft forever +``` + +This will display the IP addresses assigned to your network interface(s), +including the IP address of the installer. In the example output below, the +installer's IP addresses are `10.0.2.15`, `fec0::5054:ff:fe12:3456`, and +`fe80::5054:ff:fe12:3456%eth0`: + +To test if you can connect and your password works, you can use the following +SSH command (replace the IP address with your own): + +``` +ssh -v nixos@fec0::5054:ff:fe12:3456 +``` + +You can then use the IP address to run `nixos-anywhere` like this: + +``` +nix run github:nix-community/nixos-anywhere -- --flake '.#myconfig' --target-host nixos@fec0::5054:ff:fe12:3456 +``` + +This example assumes a flake in the current directory containing a configuration +named `myconfig`. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/secrets.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/secrets.md new file mode 100644 index 0000000..8d95ee7 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/secrets.md @@ -0,0 +1,76 @@ +# Secrets and full disk encryption + +The `nixos-anywhere` utility offers the capability to install secrets onto a +target machine. This feature is particularly beneficial when you want to +bootstrap secrets management tools such as +[sops-nix](https://github.com/Mic92/sops-nix) or +[agenix](https://github.com/ryantm/agenix), which rely on machine-specific +secrets to decrypt other uploaded secrets. + +## Example: Decrypting an OpenSSH Host Key with pass + +In this example, we demonstrate how to use a script to decrypt an OpenSSH host +key from the `pass` password manager and subsequently pass it to +`nixos-anywhere` during the installation process: + +```bash +#!/usr/bin/env bash + +# Create a temporary directory +temp=$(mktemp -d) + +# Function to cleanup temporary directory on exit +cleanup() { + rm -rf "$temp" +} +trap cleanup EXIT + +# Create the directory where sshd expects to find the host keys +install -d -m755 "$temp/etc/ssh" + +# Decrypt your private key from the password store and copy it to the temporary directory +pass ssh_host_ed25519_key > "$temp/etc/ssh/ssh_host_ed25519_key" + +# Set the correct permissions so sshd will accept the key +chmod 600 "$temp/etc/ssh/ssh_host_ed25519_key" + +# Install NixOS to the host system with our secrets +nixos-anywhere --extra-files "$temp" --flake '.#your-host' --target-host root@yourip +``` + +## Example: Uploading Disk Encryption Secrets + +In a similar vein, `nixos-anywhere` can upload disk encryption secrets, which +are necessary during formatting with disko. Here's an example that demonstrates +how to provide your disk encryption password as a file or via the `pass` utility +to `nixos-anywhere`: + +```bash +# Write your disk encryption password to a file +echo "my-super-safe-password" > /tmp/disk-1.key + +# Call nixos-anywhere with disk encryption keys +nixos-anywhere \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(pass my-disk-encryption-password) \ + --flake '.#your-host' \ + root@yourip +``` + +In the above example, replace `"my-super-safe-password"` with your actual +encryption password, and `my-disk-encryption-password` with the relevant entry +in your pass password store. Also, ensure to replace `'.#your-host'` and +`root@yourip` with your actual flake and IP address, respectively. + +## Example: Using existing SSH host keys + +If the system contains existing trusted `/etc/ssh/ssh_host_*` SSH host keys and +certificates, `nixos-anywhere` can copy them in case they are necessary during +installation and system activation. + +``` +nixos-anywhere --copy-host-keys --flake '.#your-host' root@yourip +``` + +This would copy `/etc/ssh/ssh_host_*` to `/mnt` after kexec but before +installation, ignoring files that already exist in destination. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/terraform.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/terraform.md new file mode 100644 index 0000000..8797493 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/terraform.md @@ -0,0 +1,23 @@ +# Terraform + +The nixos-anywhere terraform modules allow you to use Terraform for installing +and updating NixOS. It simplifies the deployment process by integrating +nixos-anywhere functionality. + +Our terraform module requires the +[null](https://registry.terraform.io/providers/hashicorp/null/latest) and +[external](https://registry.terraform.io/providers/hashicorp/external/latest) +provider. + +You can get these by from nixpkgs like this: + +```nix +nix-shell -p '(pkgs.terraform.withPlugins (p: [ p.null p.external ]))' +``` + +You can add this expression the `packages` list in your devshell in flake.nix or +in shell.nix. + +Checkout out the +[module reference](https://github.com/nix-community/nixos-anywhere/tree/main/terraform) +for examples and module parameter on how to use the modules. diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/howtos/use-without-flakes.md b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/use-without-flakes.md new file mode 100644 index 0000000..33419a9 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/howtos/use-without-flakes.md @@ -0,0 +1,82 @@ +# Use without flakes + +First, +[import the disko NixOS module](https://github.com/nix-community/disko/blob/master/docs/HowTo.md#installing-nixos-module) +in your NixOS configuration and define disko devices as described in the +[examples](https://github.com/nix-community/disko/tree/master/example). + +Let's assume that your NixOS configuration lives in `configuration.nix` and your +target machine is called `machine`: + +## 1. Download your favourite disk layout: + +See https://github.com/nix-community/disko-templates/ for more examples: + +The example below will work with both UEFI and BIOS-based systems. + +```bash +curl https://raw.githubusercontent.com/nix-community/disko-templates/main/single-disk-ext4/disko-config.nix > ./disko-config.nix +``` + +## 2. Get a hardware-configuration.nix from on the target machine + +- **Option 1**: If NixOS is not installed, boot into an installer without first + installing NixOS. +- **Option 2**: Use the kexec tarball method, as described + [here](https://github.com/nix-community/nixos-images#kexec-tarballs). + +- **Generate Configuration**: Run the following command on the target machine: + + ```bash + nixos-generate-config --no-filesystems --dir /tmp/config + ``` + +This creates the necessary configuration files under `/tmp/config/`. Copy +`/tmp/config/nixos/hardware-configuration.nix` to your local machine into the +same directory as `disko-config.nix`. + +## 3. Set NixOS version to use + +```nix +# default.nix +let + # replace nixos-24.11 with your preferred nixos version or revision from here: https://status.nixos.org/ + nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-24.11.tar.gz"; +in +import (nixpkgs + "/nixos/lib/eval-config.nix") { + modules = [ ./configuration.nix ]; +} +``` + +## 4. Write a NixOS configuration + +```nix +# configuration.nix +{ + imports = [ + "${fetchTarball "https://github.com/nix-community/disko/tarball/master"}/module.nix" + ./disko-config.nix + ./hardware-configuration.nix + ]; + # Replace this with the system of the installation target you want to install!!! + disko.devices.disk.main.device = "/dev/sda"; + + # Set this to the NixOS version that you have set in the previous step. + # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . + system.stateVersion = "24.11"; +} +``` + +## 5. Build and deploy with nixos-anywhere + +Your current directory now should contain the following files from the previous +step: + +- `configuration.nix`, `default.nix`, `disko-config.nix` and + `hardware-configuration.nix` + +Run `nixos-anywhere` as follows: + +```bash +nixos-anywhere --store-paths $(nix-build -A config.system.build.formatScript -A config.system.build.toplevel --no-out-link) root@machine +``` diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/logo.png b/launch/.terraform/modules/pixelfed.deploy/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..434ad443ac1a065b53f74353187f7baaa45bcf29 GIT binary patch literal 29405 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 F{2%b`V#WXf literal 0 HcmV?d00001 diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/logo.svg b/launch/.terraform/modules/pixelfed.deploy/docs/logo.svg new file mode 100644 index 0000000..204dde7 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/logo.svg @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="512" + height="512" + id="svg322" + sodipodi:docname="logo.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + inkscape:export-filename="logo.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#"> + <defs + id="defs326"> + <marker + markerWidth="512" + markerHeight="512" + refX="256" + refY="256" + orient="auto" + id="marker11007"> + <path + d="M 0,0 H 512 V 512 H 0 Z" + fill="#fbfdfc" + id="path246" /> + </marker> + </defs> + <sodipodi:namedview + id="namedview324" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:showpageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="2.2304247" + inkscape:cx="32.280848" + inkscape:cy="129.79591" + inkscape:window-width="3840" + inkscape:window-height="2090" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg322" /> + <path + d="M0,0 L4,2 L18,10 L27,18 L30,26 L30,34 L27,46 L23,60 L17,73 L8,94 L3,105 L3,116 L7,119 L13,121 L39,126 L55,131 L64,137 L68,142 L70,146 L70,154 L66,162 L57,170 L45,176 L37,179 L20,182 L11,183 L-17,184 L-24,192 L-34,206 L-47,221 L-59,232 L-73,241 L-85,246 L-93,248 L-113,248 L-127,245 L-132,243 L-132,241 L-123,235 L-113,226 L-105,218 L-96,205 L-88,189 L-82,168 L-80,136 L-72,125 L-54,98 L-38,71 L-23,44 L-13,25 L-5,9 Z " + fill="#517ED0" + transform="translate(435,104)" + id="path248" /> + <path + d="M0,0 L10,1 L19,6 L31,17 L40,26 L49,37 L57,48 L67,62 L75,74 L91,75 L107,78 L127,85 L141,93 L153,105 L160,115 L167,130 L170,139 L171,149 L162,146 L155,143 L138,139 L110,139 L93,143 L87,146 L80,146 L71,144 L57,142 L22,140 L-23,140 L-45,141 L-65,143 L-74,143 L-81,135 L-83,129 L-83,117 L-79,109 L-73,102 L-63,95 L-46,87 L-28,82 L-11,79 L14,79 L11,71 L-2,48 L-10,34 L-14,23 L-14,13 L-11,7 L-5,2 Z " + fill="#517DD0" + transform="translate(132,13)" + id="path250" /> + <path + d="M0,0 L11,2 L20,5 L29,7 L55,7 L72,4 L80,2 L104,7 L126,9 L142,10 L234,10 L240,14 L245,20 L246,24 L246,33 L241,43 L234,50 L221,58 L205,65 L183,70 L162,73 L151,73 L159,87 L174,112 L179,122 L180,125 L180,136 L175,145 L167,150 L153,150 L143,143 L130,130 L117,113 L107,99 L95,80 L90,76 L71,74 L50,69 L34,62 L23,54 L15,46 L8,34 L2,17 L0,7 Z " + fill="#4DB0CE" + transform="translate(213,350)" + id="path252" /> + <path + d="M0,0 L2,0 L7,17 L12,30 L21,45 L31,57 L39,64 L46,71 L47,73 L49,89 L54,107 L60,122 L72,147 L83,167 L99,191 L113,212 L117,220 L117,234 L114,240 L111,243 L80,243 L63,231 L50,219 L38,207 L31,199 L30,196 L27,196 L25,200 L11,215 L2,224 L-9,230 L-13,231 L-21,231 L-27,227 L-32,221 L-36,210 L-36,203 L-28,186 L-22,174 L-17,159 L-13,141 L-12,131 L-12,99 L-16,64 L-18,49 L-18,34 L-15,24 L-8,11 Z " + fill="#517ED0" + transform="translate(158,269)" + id="path256" /> + <path + d="M0,0 L12,1 L28,8 L40,16 L50,24 L65,37 L75,48 L77,51 L79,51 L89,37 L98,27 L107,18 L117,14 L126,14 L134,19 L139,25 L142,32 L142,40 L137,54 L128,74 L123,88 L118,109 L117,117 L117,147 L121,169 L124,181 L124,200 L120,211 L111,225 L106,229 L100,214 L92,199 L80,183 L70,173 L64,168 L62,153 L57,139 L49,123 L34,97 L21,75 L6,53 L-6,36 L-12,26 L-13,23 L-13,11 L-9,5 L-4,1 Z " + fill="#4EB0CF" + transform="translate(246,0)" + id="path258" /> + <path + d="M0,0 L6,0 L6,2 L11,1 L22,1 L32,6 L41,8 L53,8 L60,16 L62,17 L61,22 L56,28 L54,33 L54,43 L58,45 L58,48 L52,47 L46,42 L46,40 L30,45 L24,53 L24,61 L25,62 L31,62 L35,58 L39,59 L41,62 L43,71 L44,72 L50,72 L53,80 L53,82 L61,80 L65,78 L70,78 L72,80 L82,79 L92,84 L103,95 L106,98 L108,99 L117,100 L123,105 L123,114 L118,128 L110,142 L99,156 L88,165 L77,173 L60,180 L55,180 L56,176 L65,170 L67,166 L67,151 L72,144 L73,141 L73,134 L69,129 L65,127 L55,126 L47,116 L45,112 L45,102 L49,97 L55,94 L55,87 L49,83 L44,78 L34,72 L18,64 L11,58 L4,42 L0,35 L1,26 L2,19 L-2,16 L-8,13 L-9,12 L-9,6 L-6,2 Z " + fill="#4D99CF" + transform="translate(223,166)" + id="path260" /> + <path + d="M0,0 L9,0 L20,8 L36,14 L44,17 L46,20 L46,27 L42,33 L42,44 L47,49 L46,52 L39,52 L25,46 L15,37 L6,27 L0,17 L-3,10 L-2,2 Z " + fill="#4FABD1" + transform="translate(176,284)" + id="path262" + style="fill:#4d99cf;fill-opacity:1" /> + <path + d="M0,0 L14,0 L23,1 L29,5 L30,6 L30,11 L25,10 L24,9 L24,4 L-1,4 Z " + fill="#438ED7" + transform="translate(247,157)" + id="path266" /> + <path + id="path268" + transform="translate(203,179)" + d="m 0,0 3,1 1,8 h 8 v 4 l -2,1 H 2 L -2,9 V 2 Z" + style="fill:#4d99cf;fill-opacity:1" /> + <path + id="path288" + transform="translate(278,222)" + d="M 0,0 6,1 7,4 9,5 H 2 L -1,3 Z m 0,0 6,1 6,2 8,1 V 7 H 7 L 0,4 Z" + style="fill:#4d99cf;fill-opacity:1" /> + <path + d="M0,0 L3,1 L-7,8 L-9,8 L-9,6 L-5,5 L-4,2 Z " + fill="#6184C5" + transform="translate(371,336)" + id="path304" + style="fill:#517ed0;fill-opacity:1" /> + <path + id="path314" + style="fill:#4eb0cf;fill-opacity:1" + transform="translate(113,201)" + d="M 0,0 H 2 L 0,4 -2.8602037,5.4301018 -4,6 V 8 L -6,7 Z m 48,-40 h 25 l 14,3 4,2 -1,3 -10,8 -10,11 -9,14 -7,14 -5,17 -2,11 -1,15 -8,10 -12,21 -11,19 -6,10 -11,19 -12,22 -11,21 -9,20 -5,1 -10,-5 -8,-4 -4,-6 -1,-3 v -18 l 6,-26 5,-13 5,-12 6,-13 2,-5 v -6 l -5,-3 -29,-4 -18,-6 -10,-6 -5,-5 -2,-4 V 50 l 7,-8 10,-7 18,-6 10,-2 10,-1 18.016033,-0.600534 L -22,25 l 5,-5 8,-9 7,-9 8,-10 9,-10 9,-9 10,-7 8,-4 z" /> + <metadata + id="metadata148"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <cc:license + rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" /> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/publicdomain/zero/1.0/"> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + </cc:License> + </rdf:RDF> + </metadata> +</svg> diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/quickstart.md b/launch/.terraform/modules/pixelfed.deploy/docs/quickstart.md new file mode 100644 index 0000000..5eb1a10 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/quickstart.md @@ -0,0 +1,334 @@ +# Quickstart Guide: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" width="150" height="150"> + +[Documentation Index](./INDEX.md) + +## Introduction + +This guide documents a simple installation of NixOS using **nixos-anywhere** on +a target machine running x86_64 Linux with +[kexec](https://man7.org/linux/man-pages/man8/kexec.8.html) support. The example +used in this guide installs NixOS on a Hetzner cloud machine. The configuration +may be different for some other instances. We will be including further examples +in the [How To Guide](./howtos/INDEX.md) as and when they are available. + +You will need: + +- A [flake](https://wiki.nixos.org/wiki/Flakes) that controls the actions to be + performed +- A disk configuration containing details of the file system that will be + created on the new server. +- A target machine that is reachable via SSH, either using keys or a password, + and the privilege to either log in directly as root or a user with + password-less sudo. + +**nixos-anywhere** doesn’t need to be installed. You can run it directly from +[the Github repository.](https://github.com/nix-community/nixos-anywhere) + +Details of the flake, the disk configuration and the CLI command are discussed +below. + +## Steps required to run nixos-anywhere + +### 1. Enable Flakes + +Check if your nix has flakes enabled by running `nix flake`. It will tell you if +it's not. To enable flakes, refer to the +[NixOS Wiki](https://wiki.nixos.org/wiki/Flakes#enable-flakes). + +### 2. Initialize a Flake + +The easiest way to start is to copy our +[example flake.nix](https://github.com/nix-community/nixos-anywhere-examples/blob/main/flake.nix) +into a new directory. This example is tailored for a virtual machine setup +similar to one on [Hetzner Cloud](https://www.hetzner.com/cloud), so you might +need to adapt it for your setup. + +If you already have a flake, you can use it by adding +[disko configuration](https://github.com/nix-community/disko?tab=readme-ov-file#how-to-use-disko) +to it. + +### 3. Configure your SSH key + +If you cloned +[our nixos-anywhere-example](https://github.com/nix-community/nixos-anywhere-examples/blob/main/configuration.nix) +you will also replace the SSH key like this: In your configuration, locate the +line that reads: + +```bash +# change this to your ssh key + "CHANGE" +``` + +Replace the text `CHANGE` with your own SSH key. This is crucial, as you will +not be able to log into the target machine post-installation without it. If you +have a .pem file you can run + +```bash +ssh-keygen -y -f /path/to/your/key.pem +``` + +then paste the result in between the quotes like "ssh-rsa AAA..." + +### 4. Configure Storage + +In the same directory, create a file called `disk-config.nix`. This file will +define the disk layout for the +[disko](https://github.com/nix-community/disko/blob/master/docs/INDEX.md) tool, +which is used by nixos-anywhere to partition, format, and mount the disks. + +For a basic installation, you can copy the contents from the example provided +[here](https://github.com/nix-community/nixos-anywhere-examples/blob/main/disk-config.nix). +This configuration sets up a standard GPT (GUID Partition Table) that is +compatible with both EFI and BIOS systems and mounts the disk as `/dev/sda`. You +may need to adjust `/dev/sda` to match the correct disk on your machine. To +identify the disk, run the `lsblk` command and replace `sda` with the actual +disk name. + +For example, on this machine, we would select `/dev/nvme0n1` as the disk: + +``` +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS +nvme0n1 259:0 0 1.8T 0 disk +``` + +If this setup does not match your requirements, you can choose an example that +better suits your disk layout from the +[disko examples](https://github.com/nix-community/disko/tree/master/example). +For more detailed information, refer to the +[disko documentation](https://github.com/nix-community/disko). + +### 5. Lock your Flake + +``` +nix flake lock +``` + +This will download your flake dependencies and make a `flake.lock` file that +describes how to reproducibly build your system. + +Optionally, you can commit these files to a repo such as Github, or you can +simply reference your local directory when you run **nixos-anywhere**. This +example uses a local directory on the source machine. + +### 6. Connectivity to the Target Machine + +**nixos-anywhere** will create a temporary SSH key to use for the installation. +If your SSH key is not found, you will be asked for your password. If you are +using a non-root user, you must have access to sudo without a password. To avoid +SSH password prompts, set the `SSHPASS` environment variable to your password +and add `--env-password` to the `nixos-anywhere` command. If providing a +specific SSH key through `-i` (identity_file), this key will then be used for +the installation and no temporary SSH key will be created. + +### 7. (Optional) Test your NixOS and Disko configuration + +Skip this step and continue with Step 8, if you don't have a hardware +configuration (hardware-configuration.nix or facter.json) generated yet or make +sure you don't import non-existing hardware-configuration.nix or facter.json +during running the vm test. + +The following command will automatically test your nixos configuration and run +disko inside a virtual machine, where + +- `<path to configuration>` is the path to the directory or repository + containing `flake.nix` and `disk-config.nix` + +- `<configuration name>` must match the name that immediately follows the text + `nixosConfigurations.` in the flake, as indicated by the comment in the + [example](https://github.com/nix-community/nixos-anywhere-examples/blob/main/flake.nix). + +``` +nix run github:nix-community/nixos-anywhere -- --flake <path to configuration>#<configuration name> --vm-test +``` + +### 8. Prepare Hardware Configuration + +If you're not using a virtual machine, it's recommended to allow +`nixos-anywhere` to generate a hardware configuration during installation. This +ensures that essential drivers, such as those required for disk detection, are +properly configured. + +To enable `nixos-anywhere` to integrate its generated configuration into your +NixOS setup, you need to include an import for the hardware configuration +beforehand. + +Here’s an example: + +```diff + nixosConfigurations.generic = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + disko.nixosModules.disko + ./configuration.nix ++ ./hardware-configuration.nix + ]; + }; +``` + +When running `nixos-anywhere`, this file is automatically generated by including +the following flags in your command: +`--generate-hardware-config nixos-generate-config ./hardware-configuration.nix`. +The second flag, `./hardware-configuration.nix`, specifies where +`nixos-generate-config` will store the configuration. Adjust this path to +reflect the location where you want the `hardware-configuration.nix` for this +machine to be saved. + +#### 8.1 nixos-facter + +As an alternative to `nixos-generate-config`, you can use the experimental +[nixos-facter](https://github.com/numtide/nixos-facter) command, which offers +more comprehensive hardware reports and advanced configuration options. + +To use `nixos-facter`, add the following to your flake inputs: + +```diff + { ++ inputs.nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; + } +``` + +Next, import the module into your configuration and specify `facter.json` as the +path where the hardware report will be saved: + +```diff + nixosConfigurations.generic-nixos-facter = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + disko.nixosModules.disko + ./configuration.nix ++ nixos-facter-modules.nixosModules.facter ++ { config.facter.reportPath = ./facter.json } + ]; + }; +``` + +To generate the configuration for `nixos-facter` with `nixos-anywhere`, use the +following flags: `--generate-hardware-config nixos-facter ./facter.json`. The +second flag, `./facter.json`, specifies where `nixos-generate-config` will store +the hardware report. Adjust this path to suit the location where you want the +`facter.json` to be saved. + +### 9. Run it + +You can now run **nixos-anywhere** from the command line as shown below, where: + +- `<path to configuration>` is the path to the directory or repository + containing `flake.nix` and `disk-config.nix` + +- `<configuration name>` must match the name that immediately follows the text + `nixosConfigurations.` in the flake, as indicated by the comment in the + [example](https://github.com/nix-community/nixos-anywhere-examples/blob/main/flake.nix). + +- `<ip address>` is the IP address of the target machine. + +``` +nix run github:nix-community/nixos-anywhere -- --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +The command would look like this if you had created your files in a directory +named `/home/mydir/test` and the IP address of your target machine is +`37.27.18.135`: + +``` +nix run github:nix-community/nixos-anywhere -- --flake /home/mydir/test#hetzner-cloud --target-host root@37.27.18.135 +``` + +If you also need to generate hardware configuration amend flags for +nixos-generate-config: + +``` +nix run github:nix-community/nixos-anywhere -- --generate-hardware-config nixos-generate-config ./hardware-configuration.nix --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +Or these flags if you are using nixos-facter instead: + +``` +nix run github:nix-community/nixos-anywhere -- --generate-hardware-config nixos-facter ./facter.json --flake <path to configuration>#<configuration name> --target-host root@<ip address> +``` + +Adjust the location of `./hardware-configuration.nix` and `./facter.json` +accordingly. + +**nixos-anywhere** will then run, showing various output messages at each stage. +It may take some time to complete, depending on Internet speeds. It should +finish by showing the messages below before returning to the command prompt. + +``` +Installation finished. No error reported. +Warning: Permanently added '<ip-address>' (ED25519) to the list of known hosts +``` + +When this happens, the target server will have been overwritten with a new +installation of NixOS. Note that the server's public SSH key will have changed. + +If you have previously accessed this server using SSH, you may see the following +message the next time you try to log in to the target. + +``` +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! +Someone could be eavesdropping on you right now (man-in-the-middle attack)! +It is also possible that a host key has just been changed. +The fingerprint for the ED25519 key sent by the remote host is +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. +Please contact your system administrator. +Add correct host key in ~/.ssh/known_hosts to get rid of this message. +Offending ECDSA key in ~/.ssh/known_hosts:6 + remove with: + ssh-keygen -f ~/.ssh/known_hosts" -R "<ip address>" +Host key for <ip_address> has changed and you have requested strict checking. +Host key verification failed. +``` + +This is because the `known_hosts` file in the `.ssh` directory now contains a +mismatch, since the server has been overwritten. To solve this, use a text +editor to remove the old entry from the `known_hosts` file (or use the command +`ssh-keygen -R <ip_address>`). The next connection attempt will then treat this +as a new server. + +The error message line `Offending ECDSA key in ~/.ssh/known_hosts:6` gives the +line number that needs to be removed from the `known_hosts` file (line 6 in this +example). + +# Finished! + +**nixos-anywhere**'s job is now done, as it is a tool to install NixOS onto the +target machine. + +Any future changes to the configuration should be made to your flake. You would +reference this flake when using the NixOS `nixos-rebuild` command or a separate +3rd party deployment tool of your choice i.e. +[deploy-rs](https://github.com/serokell/deploy-rs), +[colmena](https://github.com/zhaofengli/colmena), +[nixinate](https://github.com/MatthewCroughan/nixinate), +[clan](https://clan.lol/) (author's choice). + +To update on the machine locally (replace `<URL to your flake>` with your flake +i.e. `.#` if your flake is in the current directory): + +``` +nixos-rebuild switch --flake <URL to your flake> +``` + +To update remotely you will need to have configured an +[ssh server](https://search.nixos.org/options?show=services.sshd.enable) and +your ssh key for the +[root user](https://search.nixos.org/options?show=users.users.%3Cname%3E.openssh.authorizedKeys.keys): + +``` +nixos-rebuild switch --flake <URL to your flake> --target-host "root@<ip address>" +``` + +See the Nix documentation for use of the flake +[URL-like syntax](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake#url-like-syntax). + +For more information on different use cases of **nixos-anywhere** please refer +to the [How to Guide](./howtos/INDEX.md), and for more technical information and +explanation of known error messages, refer to the +[Reference Manual](./reference.md). diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/reference.md b/launch/.terraform/modules/pixelfed.deploy/docs/reference.md new file mode 100644 index 0000000..8391657 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/reference.md @@ -0,0 +1,137 @@ +# Reference Manual: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img title="" src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" alt="" width="141"> + +[Documentation Index](./INDEX.md) + +TODO: Populate this guide properly + +## Contents + +[Command Line Usage](#command-line-usage) + +[Explanation of known error messages](#explanation-of-known-error-messages) + +## Command Line Usage + +<!-- `$ bash ./src/nixos-anywhere.sh --help` --> + +``` +Usage: nixos-anywhere [options] [<ssh-host>] + +Options: + +* -f, --flake <flake_uri> + set the flake to install the system from. i.e. + nixos-anywhere --flake .#mymachine + Also supports variants: + nixos-anywhere --flake .#nixosConfigurations.mymachine.config.virtualisation.vmVariant +* --target-host <ssh-host> + set the SSH target host to deploy onto. +* -i <identity_file> + selects which SSH private key file to use. +* -p, --ssh-port <ssh_port> + set the ssh port to connect with +* --ssh-option <ssh_option> + set an ssh option +* -L, --print-build-logs + print full build logs +* --env-password + set a password used by ssh-copy-id, the password should be set by + the environment variable SSHPASS +* -s, --store-paths <disko-script> <nixos-system> + set the store paths to the disko-script and nixos-system directly + if this is given, flake is not needed +* --kexec <path> + use another kexec tarball to bootstrap NixOS +* --kexec-extra-flags + extra flags to add into the call to kexec, e.g. "--no-sync" +* --ssh-store-setting <key> <value> + ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded. +* --post-kexec-ssh-port <ssh_port> + after kexec is executed, use a custom ssh port to connect. Defaults to 22 +* --copy-host-keys + copy over existing /etc/ssh/ssh_host_* host keys to the installation +* --extra-files <path> + contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten + Copied files will be owned by root unless specified by --chown option. See documentation for details. +* --chown <path> <ownership> + change ownership of <path> recursively. Recommended to use uid:gid as opposed to username:groupname for ownership. + Option can be specified more than once. +* --disk-encryption-keys <remote_path> <local_path> + copy the contents of the file or pipe in local_path to remote_path in the installer environment, + after kexec but before installation. Can be repeated. +* --no-substitute-on-destination + disable passing --substitute-on-destination to nix-copy +* --debug + enable debug output +* --show-trace + show nix build traces +* --option <key> <value> + nix option to pass to every nix related command +* --from <store-uri> + URL of the source Nix store to copy the nixos and disko closure from +* --build-on-remote + build the closure on the remote machine instead of locally and copy-closuring it +* --vm-test + build the system and test the disk configuration inside a VM without installing it to the target. +* --generate-hardware-config nixos-facter|nixos-generate-config <path> + generate a hardware-configuration.nix file using the specified backend and write it to the specified path. + The backend can be either 'nixos-facter' or 'nixos-generate-config'. +* --phases + comma separated list of phases to run. Default is: kexec,disko,install,reboot + kexec: kexec into the nixos installer + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode + install: install the system + reboot: unmount the filesystems, export any ZFS pools and reboot the machine +* --disko-mode disko|mount|format + set the disko mode to format, mount or destroy. Default is disko. + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode +* --no-disko-deps + This will only upload the disko script and not the partitioning tools dependencies. + Installers usually have dependencies available. + Use this option if your target machine has not enough RAM to store the dependencies in memory. +* --build-on auto|remote|local + sets the build on settings to auto, remote or local. Default is auto. + auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build + local: will build on the local host + remote: will build on the remote host +``` + +## Explanation of known error messages + +TODO: Add additional error messages and meanings. Fill in missing explanations + +This section lists known error messages and their explanations. Some +explanations may refer to the following CLI syntax: + +`nix run github:nix-community/nixos-anywhere -- --flake <path to configuration>#<configuration name> root@<ip address>` + +This list is not comprehensive. It's possible you may encounter errors that +originate from the underlying operating system. These should be documented in +the relevant operating system manual. + +| Id | Message | Explanation | +| -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 1 | Failure unpacking initrd | You don't have enough RAM to hold `kexec` | +| 2 | Flake <flake_url> does not provide attribute | The configuration name you specified in your flake URI is not defined as a NixOS configuration in your flake eg if your URI was mydir#myconfig, then myconfig should be included in the flake as `nixosConfigurations.myconfig` | +| 3 | Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri. | As for error #2 | +| | For example, to use the output nixosConfigurations.foo from the flake.nix, append "#foo" to the flake-uri | | +| 4 | Retrieving host facts via ssh failed. Check with --debug for the root cause, unless you have done so already | TODO: Explain | +| 5 | ssh-host must be set | <ip_address> has not been supplied | +| 6 | <disko_script> and <nixos_system> must be existing store-paths | This occurs if the -s switch has been used to specify the disko script and store path correctly, and the scripts cannot be found at the given URI | +| 7 | flake must be set | This occurs if both the -flake option (use a flake) and the -s option (specify paths directly) have been omitted. Either one or the other must be specified. | +| 8 | no tar command found, but required to unpack kexec tarball | The destination machine does not have a `tar` command available. This is needed to unpack the `kexec`. | +| 9 | no setsid command found, but required to run the kexec script under a new session | The destination machine does not have the `setsid` command available | +| 10 | This script requires Linux as the operating system, but got <operating system> | The destination machine is not running Linux | +| 11 | The default kexec image only support x86_64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information. | By default, `nixos-anywhere` uses its own `kexec` image, which will only run on x86_64 CPUs. For other CPU types, you can use your own `kexec` image instead. Refer to the [How To Guide](./howtos#using-your-own-kexec-image) for instructions. | +| 12 | Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri. | This is a `disko` error. As for Error #2 | +| | For example, to use the output diskoConfigurations.foo from the flake.nix, append \"#foo\" to the flake-uri. | | +| 13 | mode must be either create, mount or zap_create_mount | This is a `disko` error. The `disko` switches have not been used correctly. This could happen if you supplied your own `disko` script using the -s option | +| 14 | disko config must be an existing file or flake must be set | This is a `disko` error. This will happen if the `disko.devices` entry in your flake doesn't match the name of a file in the same location as your flake. | +| | | | +| | | | +| | | | +| | | | diff --git a/launch/.terraform/modules/pixelfed.deploy/docs/requirements.md b/launch/.terraform/modules/pixelfed.deploy/docs/requirements.md new file mode 100644 index 0000000..67b14f4 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/docs/requirements.md @@ -0,0 +1,39 @@ +# System Requirements: nixos-anywhere + +**_Install NixOS everywhere via ssh_** + +<img src="https://raw.githubusercontent.com/nix-community/nixos-anywhere/main/docs/logo.png" width="150" height="150"> + +[Documentation Index](./INDEX.md) + +## Requirements + +### Source Machine + +1. **Supported Systems:** + - Linux or macOS computers with Nix installed. + - NixOS + - Windows systems using WSL2. + +2. **Nix Installation:** If Nix is not yet installed on your system, refer to + the [nix installation page](https://nixos.org/download#download-nix). + +### Destination Machine + +The machine must be reachable over the public internet or local network. +Nixos-anywhere does not support wifi networks. If a VPN is needed, define a +custom installer via the --kexec flag which connects to your VPN. + +1. **Direct Boot Option:** + - Must be already running a NixOS installer. + +2. **Alternative Boot Options:** If not booting directly from a NixOS installer + image: + - **Architecture & Support:** Must be operating on: + - x86-64 or aarch64 Linux systems with kexec support. Note: While most + x86-64 Linux systems support kexec, if you're using an architecture other + than those mentioned, you may need to specify a + [different kexec image](./howtos/INDEX.md#using-your-own-kexec-image) + manually. + - **Memory Requirements:** + - At least 1 GB of RAM (excluding swap space). diff --git a/launch/.terraform/modules/pixelfed.deploy/flake.lock b/launch/.terraform/modules/pixelfed.deploy/flake.lock new file mode 100644 index 0000000..5a7232c --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/flake.lock @@ -0,0 +1,132 @@ +{ + "nodes": { + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741786315, + "narHash": "sha256-VT65AE2syHVj6v/DGB496bqBnu1PXrrzwlw07/Zpllc=", + "owner": "nix-community", + "repo": "disko", + "rev": "0d8c6ad4a43906d14abd5c60e0ffe7b587b213de", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "master", + "repo": "disko", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741352980, + "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixos-images": { + "inputs": { + "nixos-stable": [ + "nixos-stable" + ], + "nixos-unstable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741866599, + "narHash": "sha256-Re/T1Cjmiis0tdphj/Wjqt+c2RlMw/il7LBWzvwQPz0=", + "owner": "nix-community", + "repo": "nixos-images", + "rev": "63285ff93fc1daa2caac9f86e2302ae4edc5e84f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-images", + "type": "github" + } + }, + "nixos-stable": { + "locked": { + "lastModified": 1741862977, + "narHash": "sha256-prZ0M8vE/ghRGGZcflvxCu40ObKaB+ikn74/xQoNrGQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "cdd2ef009676ac92b715ff26630164bb88fec4e0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1742051767, + "narHash": "sha256-JpyjnalnIqJ7cvP8HzaoJN9/i2bDx83dToodHHjGuNg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ec886d10b507760c90ed01e2eac7f0679d0a47ae", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable-small", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "disko": "disko", + "flake-parts": "flake-parts", + "nixos-images": "nixos-images", + "nixos-stable": "nixos-stable", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1739829690, + "narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "3d0579f5cc93436052d94b73925b48973a104204", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/launch/.terraform/modules/pixelfed.deploy/flake.nix b/launch/.terraform/modules/pixelfed.deploy/flake.nix new file mode 100644 index 0000000..d60191c --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/flake.nix @@ -0,0 +1,55 @@ +{ + description = "A universal nixos installer, just needs ssh access to the target system"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable-small"; + flake-parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + + # used for testing + disko = { + url = "github:nix-community/disko/master"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nixos-stable.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixos-images.url = "github:nix-community/nixos-images"; + nixos-images.inputs.nixos-unstable.follows = "nixpkgs"; + nixos-images.inputs.nixos-stable.follows = "nixos-stable"; + + # used for development + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs: + inputs.flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + "aarch64-darwin" + ]; + imports = [ + ./src/flake-module.nix + ./tests/flake-module.nix + ./docs/flake-module.nix + # allow to disable treefmt in downstream flakes + ] ++ inputs.nixpkgs.lib.optional (inputs.treefmt-nix ? flakeModule) ./treefmt/flake-module.nix; + + perSystem = + { self', lib, ... }: + { + checks = + let + packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages; + devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells; + in + packages // devShells; + }; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/scripts/create-release.sh b/launch/.terraform/modules/pixelfed.deploy/scripts/create-release.sh new file mode 100755 index 0000000..7f20303 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/scripts/create-release.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env nix +#! nix shell nixpkgs#bash nixpkgs#gnused --command bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$SCRIPT_DIR/.." + +version=${1:-} +if [[ -z $version ]]; then + echo "USAGE: $0 version" >&2 + exit 1 +fi + +if [[ "$(git symbolic-ref --short HEAD)" != "main" ]]; then + echo "must be on main branch" >&2 + exit 1 +fi + +# ensure we are up-to-date +uncommitted_changes=$(git diff --compact-summary) +if [[ -n $uncommitted_changes ]]; then + echo -e "There are uncommitted changes, exiting:\n${uncommitted_changes}" >&2 + exit 1 +fi +git pull git@github.com:nix-community/nixos-anywhere main +unpushed_commits=$(git log --format=oneline origin/main..main) +if [[ $unpushed_commits != "" ]]; then + echo -e "\nThere are unpushed changes, exiting:\n$unpushed_commits" >&2 + exit 1 +fi +sed -i -e "s!version = \".*\";!version = \"${version}\";!" src/default.nix +git add src/default.nix +nix-shell -p nix-fast-build --command "nix-fast-build --eval-workers 2" +git commit -m "bump version ${version}" +git tag "${version}" + +echo "now run 'git push --tags origin main'" diff --git a/launch/.terraform/modules/pixelfed.deploy/src/default.nix b/launch/.terraform/modules/pixelfed.deploy/src/default.nix new file mode 100644 index 0000000..c171932 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/src/default.nix @@ -0,0 +1,63 @@ +{ + stdenv, + openssh, + gitMinimal, + nixVersions, + nix, + coreutils, + curl, + gnugrep, + gnutar, + gawk, + findutils, + gnused, + sshpass, + terraform-docs, + lib, + makeWrapper, + mkShellNoCC, +}: +let + runtimeDeps = [ + gitMinimal # for git flakes + # pinned because nix-copy-closure hangs if ControlPath provided for SSH: https://github.com/NixOS/nix/issues/8480 + (if lib.versionAtLeast nix.version "2.16" then nix else nixVersions.nix_2_16) + coreutils + curl # when uploading tarballs + gnugrep + gawk + findutils + gnused # needed by ssh-copy-id + sshpass # used to provide password for ssh-copy-id + gnutar # used to upload extra-files + ]; +in +stdenv.mkDerivation { + pname = "nixos-anywhere"; + version = "1.8.0"; + src = ./..; + nativeBuildInputs = [ makeWrapper ]; + installPhase = '' + install -D --target-directory=$out/libexec/nixos-anywhere/ -m 0755 src/*.sh + + # We prefer the system's openssh over our own, since it might come with features not present in ours: + # https://github.com/nix-community/nixos-anywhere/issues/62 + makeShellWrapper $out/libexec/nixos-anywhere/nixos-anywhere.sh $out/bin/nixos-anywhere \ + --prefix PATH : ${lib.makeBinPath runtimeDeps} --suffix PATH : ${lib.makeBinPath [ openssh ]} + ''; + + # Dependencies for our devshell + passthru.devShell = mkShellNoCC { + packages = runtimeDeps ++ [ + openssh + terraform-docs + ]; + }; + + meta = with lib; { + description = "Install nixos everywhere via ssh"; + homepage = "https://github.com/nix-community/nixos-anywhere"; + license = licenses.mit; + platforms = platforms.all; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/src/flake-module.nix b/launch/.terraform/modules/pixelfed.deploy/src/flake-module.nix new file mode 100644 index 0000000..d131219 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/src/flake-module.nix @@ -0,0 +1,11 @@ +{ + perSystem = + { config, pkgs, ... }: + { + packages = { + nixos-anywhere = pkgs.callPackage ./. { }; + default = config.packages.nixos-anywhere; + }; + devShells.default = config.packages.nixos-anywhere.devShell; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/src/get-facts.sh b/launch/.terraform/modules/pixelfed.deploy/src/get-facts.sh new file mode 100755 index 0000000..8043442 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/src/get-facts.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -efu "${enableDebug:-}" +has() { + command -v "$1" >/dev/null && echo "y" || echo "n" +} +isNixos=$(if test -f /etc/os-release && grep -Eq 'ID(_LIKE)?="?nixos"?' /etc/os-release; then echo "y"; else echo "n"; fi) +cat <<FACTS +isOs=$(uname) +isArch=$(uname -m) +isKexec=$(if test -f /etc/is_kexec; then echo "y"; else echo "n"; fi) +isNixos=$isNixos +isInstaller=$(if [ "$isNixos" = "y" ] && grep -Eq 'VARIANT_ID="?installer"?' /etc/os-release; then echo "y"; else echo "n"; fi) +isContainer=$(if [ "$(has systemd-detect-virt)" = "y" ]; then systemd-detect-virt --container; else echo "none"; fi) +hasIpv6Only=$(if [ "$(has ip)" = "n" ] || ip r g 1 >/dev/null 2>/dev/null || ! ip -6 r g :: >/dev/null 2>/dev/null; then echo "n"; else echo "y"; fi) +hasTar=$(has tar) +hasCpio=$(has cpio) +hasSudo=$(has sudo) +hasDoas=$(has doas) +hasWget=$(has wget) +hasCurl=$(has curl) +hasSetsid=$(has setsid) +hasNixOSFacter=$(command -v nixos-facter >/dev/null && echo "y" || echo "n") +FACTS diff --git a/launch/.terraform/modules/pixelfed.deploy/src/nixos-anywhere.sh b/launch/.terraform/modules/pixelfed.deploy/src/nixos-anywhere.sh new file mode 100755 index 0000000..c6cadb9 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/src/nixos-anywhere.sh @@ -0,0 +1,880 @@ +#!/usr/bin/env bash +set -euo pipefail + +here=$(dirname "${BASH_SOURCE[0]}") +flake="" +flakeAttr="" +kexecUrl="" +kexecExtraFlags="" +sshStoreSettings="" +enableDebug="" +nixBuildFlags=() +diskoAttr="" +diskoScript="" +diskoMode="disko" +diskoDeps=y +nixosSystem="" +extraFiles="" +vmTest="n" +nixOptions=( + --extra-experimental-features 'nix-command flakes' + "--no-write-lock-file" +) +SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY-} + +declare -A phases +phases[kexec]=1 +phases[disko]=1 +phases[install]=1 +phases[reboot]=1 + +hardwareConfigBackend=none +hardwareConfigPath= +sshPrivateKeyFile= +if [ -t 0 ]; then # stdin is a tty, we allow interactive input to ssh i.e. passwords + sshTtyParam="-t" +else + sshTtyParam="-T" +fi +sshConnection= +postKexecSshPort=22 +buildOnRemote=n +buildOn=auto +envPassword=n + +# Facts set by get-facts.sh +isOs= +isArch= +isKexec= +isInstaller= +isContainer= +hasIpv6Only= +hasTar= +hasCpio= +hasSudo= +hasDoas= +hasWget= +hasCurl= +hasSetsid= +hasNixOSFacter= + +sshKeyDir=$(mktemp -d) +trap 'rm -rf "$sshKeyDir"' EXIT +mkdir -p "$sshKeyDir" + +declare -A diskEncryptionKeys=() +declare -A extraFilesOwnership=() +declare -a nixCopyOptions=() +declare -a sshArgs=() + +showUsage() { + cat <<USAGE +Usage: nixos-anywhere [options] [<ssh-host>] + +Options: + +* -f, --flake <flake_uri> + set the flake to install the system from. i.e. + nixos-anywhere --flake .#mymachine + Also supports variants: + nixos-anywhere --flake .#nixosConfigurations.mymachine.config.virtualisation.vmVariant +* --target-host <ssh-host> + set the SSH target host to deploy onto. +* -i <identity_file> + selects which SSH private key file to use. +* -p, --ssh-port <ssh_port> + set the ssh port to connect with +* --ssh-option <ssh_option> + set an ssh option +* -L, --print-build-logs + print full build logs +* --env-password + set a password used by ssh-copy-id, the password should be set by + the environment variable SSHPASS +* -s, --store-paths <disko-script> <nixos-system> + set the store paths to the disko-script and nixos-system directly + if this is given, flake is not needed +* --kexec <path> + use another kexec tarball to bootstrap NixOS +* --kexec-extra-flags + extra flags to add into the call to kexec, e.g. "--no-sync" +* --ssh-store-setting <key> <value> + ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded. +* --post-kexec-ssh-port <ssh_port> + after kexec is executed, use a custom ssh port to connect. Defaults to 22 +* --copy-host-keys + copy over existing /etc/ssh/ssh_host_* host keys to the installation +* --extra-files <path> + contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten + Copied files will be owned by root unless specified by --chown option. See documentation for details. +* --chown <path> <ownership> + change ownership of <path> recursively. Recommended to use uid:gid as opposed to username:groupname for ownership. + Option can be specified more than once. +* --disk-encryption-keys <remote_path> <local_path> + copy the contents of the file or pipe in local_path to remote_path in the installer environment, + after kexec but before installation. Can be repeated. +* --no-substitute-on-destination + disable passing --substitute-on-destination to nix-copy +* --debug + enable debug output +* --show-trace + show nix build traces +* --option <key> <value> + nix option to pass to every nix related command +* --from <store-uri> + URL of the source Nix store to copy the nixos and disko closure from +* --build-on-remote + build the closure on the remote machine instead of locally and copy-closuring it +* --vm-test + build the system and test the disk configuration inside a VM without installing it to the target. +* --generate-hardware-config nixos-facter|nixos-generate-config <path> + generate a hardware-configuration.nix file using the specified backend and write it to the specified path. + The backend can be either 'nixos-facter' or 'nixos-generate-config'. +* --phases + comma separated list of phases to run. Default is: kexec,disko,install,reboot + kexec: kexec into the nixos installer + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode + install: install the system + reboot: unmount the filesystems, export any ZFS pools and reboot the machine +* --disko-mode disko|mount|format + set the disko mode to format, mount or destroy. Default is disko. + disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode +* --no-disko-deps + This will only upload the disko script and not the partitioning tools dependencies. + Installers usually have dependencies available. + Use this option if your target machine has not enough RAM to store the dependencies in memory. +* --build-on auto|remote|local + sets the build on settings to auto, remote or local. Default is auto. + auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build + local: will build on the local host + remote: will build on the remote host +USAGE +} + +abort() { + echo "aborted: $*" >&2 + exit 1 +} + +step() { + echo "### $* ###" +} + +parseArgs() { + local substituteOnDestination=y + local printBuildLogs=n + local buildOnRemote=n + while [[ $# -gt 0 ]]; do + case "$1" in + -f | --flake) + flake=$2 + shift + ;; + --target-host) + sshConnection=$2 + shift + ;; + -i) + sshPrivateKeyFile=$2 + shift + ;; + -p | --ssh-port) + sshArgs+=("-p" "$2") + shift + ;; + --ssh-option) + sshArgs+=("-o" "$2") + shift + ;; + -L | --print-build-logs) + printBuildLogs=y + ;; + -s | --store-paths) + diskoScript=$(readlink -f "$2") + nixosSystem=$(readlink -f "$3") + shift + shift + ;; + --generate-hardware-config) + if [[ $# -lt 3 ]]; then + abort "Missing arguments for --generate-hardware-config <backend> <path>" + fi + case "$2" in + nixos-facter | nixos-generate-config) + hardwareConfigBackend=$2 + ;; + *) + abort "Unknown hardware config backend: $2" + ;; + esac + hardwareConfigPath=$3 + shift + shift + ;; + -t | --tty) + echo "the '$1' flag is deprecated, a tty is now detected automatically" >&2 + ;; + --help) + showUsage + exit 0 + ;; + --kexec) + kexecUrl=$2 + shift + ;; + --kexec-extra-flags) + kexecExtraFlags=$2 + shift + ;; + --ssh-store-setting) + key=$2 + shift + value=$2 + shift + sshStoreSettings+="$sshStoreSettings$key=$value&" + shift + ;; + --post-kexec-ssh-port) + postKexecSshPort=$2 + shift + ;; + --copy-host-keys) + copyHostKeys=y + ;; + --show-trace) + nixBuildFlags+=("--show-trace") + ;; + --debug) + enableDebug="-x" + printBuildLogs=y + set -x + ;; + --disko-mode) + case "$2" in + format | mount | disko) + diskoMode=$2 + ;; + *) + abort "Supported values for --disko-mode are disko, mount and format. Unknown mode : $2" + ;; + esac + + shift + ;; + --no-disko-deps) + diskoDeps=n + ;; + --build-on) + case "$2" in + auto | local | remote) + buildOn=$2 + ;; + *) + abort "Supported values for --build-on are auto, local and remote. Unknown mode : $2" + ;; + esac + + shift + ;; + --extra-files) + extraFiles=$2 + shift + ;; + --chown) + extraFilesOwnership["$2"]="$3" + shift + shift + ;; + --disk-encryption-keys) + diskEncryptionKeys["$2"]="$3" + shift + shift + ;; + --phases) + phases[kexec]=0 + phases[disko]=0 + phases[install]=0 + phases[reboot]=0 + IFS=, read -r -a phaseList <<<"$2" + for phase in "${phaseList[@]}"; do + if [[ ${phases[$phase]:-unset} == unset ]]; then + abort "Unknown phase: $phase" + fi + phases[$phase]=1 + done + shift + ;; + --stop-after-disko) + echo "WARNING: --stop-after-disko is deprecated, use --phases kexec,disko instead" 2>&1 + phases[kexec]=1 + phases[disko]=1 + phases[install]=0 + phases[reboot]=0 + ;; + --no-reboot) + echo "WARNING: --no-reboot is deprecated, use --phases kexec,disko,install instead" 2>&1 + phases[kexec]=1 + phases[disko]=1 + phases[install]=1 + phases[reboot]=0 + ;; + --from) + nixCopyOptions+=("--from" "$2") + shift + ;; + --option) + key=$2 + shift + value=$2 + shift + nixOptions+=("--option" "$key" "$value") + ;; + --no-substitute-on-destination) + substituteOnDestination=n + ;; + --build-on-remote) + echo "WARNING: --build-on-remote is deprecated, use --build-on remote instead" 2>&1 + buildOnRemote=y + buildOn="remote" + ;; + --env-password) + envPassword=y + ;; + --vm-test) + vmTest=y + ;; + *) + if [[ -z ${sshConnection} ]]; then + sshConnection="$1" + else + showUsage + exit 1 + fi + ;; + esac + shift + done + + diskoAttr="${diskoMode}Script" + + if [[ ${diskoDeps} == "n" ]]; then + diskoAttr="${diskoAttr}NoDeps" + fi + + if [[ ${printBuildLogs} == "y" ]]; then + nixOptions+=("-L") + fi + + if [[ $substituteOnDestination == "y" ]]; then + nixCopyOptions+=("--substitute-on-destination") + fi + + if [[ $vmTest == "n" ]] && [[ -z ${sshConnection} ]]; then + abort "ssh-host must be set" + fi + + if [[ $buildOn == "local" ]] && [[ $buildOnRemote == "y" ]]; then + abort "Conflicting flags: --build-on local and --build-on-remote used." + fi + + if [[ -n ${flake} ]]; then + if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then + flake="${BASH_REMATCH[1]}" + flakeAttr="${BASH_REMATCH[2]}" + fi + if [[ -z ${flakeAttr} ]]; then + echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri." >&2 + echo 'For example, to use the output nixosConfigurations.foo from the flake.nix, append "#foo" to the flake-uri.' >&2 + exit 1 + fi + + # Support .#foo shorthand + if [[ $flakeAttr != nixosConfigurations.* ]]; then + flakeAttr="nixosConfigurations.\"$flakeAttr\".config" + fi + fi + +} + +# ssh wrapper +runSshNoTty() { + ssh -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@" +} +runSshTimeout() { + timeout 10 ssh -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@" +} +runSsh() { + ssh "$sshTtyParam" -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@" +} + +nixCopy() { + NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $sshKeyDir/nixos-anywhere ${sshArgs[*]}" nix copy \ + "${nixOptions[@]}" \ + "${nixCopyOptions[@]}" \ + "$@" +} +nixBuild() { + NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $sshKeyDir/nixos-anywhere ${sshArgs[*]}" nix build \ + --print-out-paths \ + --no-link \ + "${nixBuildFlags[@]}" \ + "${nixOptions[@]}" \ + "$@" +} + +runVmTest() { + if [[ -z ${flakeAttr} ]]; then + echo "--vm-test is not supported with --store-paths" >&2 + echo "Please use --flake instead or build config.system.build.installTest of your nixos configuration manually" >&2 + exit 1 + fi + + if [[ ${buildOn} == "remote" ]]; then + echo "--vm-test is not supported with --build-on-remote" >&2 + exit 1 + fi + if [[ -n ${extraFiles} ]]; then + echo "--vm-test is not supported with --extra-files" >&2 + exit 1 + fi + if [ ${#diskEncryptionKeys[@]} -gt 0 ]; then + echo "--vm-test is not supported with --disk-encryption-keys" >&2 + exit 1 + fi + nix build \ + --print-out-paths \ + --no-link \ + -L \ + "${nixBuildFlags[@]}" \ + "${nixOptions[@]}" \ + "${flake}#${flakeAttr}.system.build.installTest" +} + +uploadSshKey() { + # ssh-copy-id requires this directory + mkdir -p "$HOME/.ssh/" + if [[ -n ${sshPrivateKeyFile} ]]; then + cp "$sshPrivateKeyFile" "$sshKeyDir/nixos-anywhere" + ssh-keygen -y -f "$sshKeyDir/nixos-anywhere" >"$sshKeyDir/nixos-anywhere.pub" + else + # we generate a temporary ssh keypair that we can use during nixos-anywhere + ssh-keygen -t ed25519 -f "$sshKeyDir"/nixos-anywhere -P "" -C "nixos-anywhere" >/dev/null + fi + + declare -a sshCopyIdArgs + if [[ -n ${sshPrivateKeyFile} ]]; then + unset SSH_AUTH_SOCK # don't use system agent if key was supplied + sshCopyIdArgs+=(-o "IdentityFile=${sshPrivateKeyFile}" -f) + fi + + step Uploading install SSH keys + until + if [[ ${envPassword} == y ]]; then + sshpass -e \ + ssh-copy-id \ + -i "$sshKeyDir"/nixos-anywhere.pub \ + -o ConnectTimeout=10 \ + -o UserKnownHostsFile=/dev/null \ + -o IdentitiesOnly=yes \ + -o StrictHostKeyChecking=no \ + "${sshCopyIdArgs[@]}" \ + "${sshArgs[@]}" \ + "$sshConnection" + else + ssh-copy-id \ + -i "$sshKeyDir"/nixos-anywhere.pub \ + -o ConnectTimeout=10 \ + -o UserKnownHostsFile=/dev/null \ + -o StrictHostKeyChecking=no \ + "${sshCopyIdArgs[@]}" \ + "${sshArgs[@]}" \ + "$sshConnection" + fi + do + sleep 3 + done +} + +importFacts() { + step Gathering machine facts + local facts filteredFacts + if ! facts=$(runSsh -o ConnectTimeout=10 enableDebug=$enableDebug sh -- <"$here"/get-facts.sh); then + exit 1 + fi + filteredFacts=$(echo "$facts" | grep -E '^(has|is)[A-Za-z0-9_]+=\S+') + if [[ -z $filteredFacts ]]; then + abort "Retrieving host facts via ssh failed. Check with --debug for the root cause, unless you have done so already" + fi + # make facts available in script + # shellcheck disable=SC2046 + export $(echo "$filteredFacts" | xargs) + + for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid; do + if [[ -z ${!var} ]]; then + abort "Failed to retrieve fact $var from host" + fi + done +} + +checkBuildLocally() { + local system extraPlatforms machineSystem + system="$(nix --extra-experimental-features 'nix-command flakes' config show system)" + extraPlatforms="$(nix --extra-experimental-features 'nix-command flakes' config show extra-platforms)" + + if [[ $# -gt 0 ]]; then + machineSystem=$1 + elif [[ -n ${nixosSystem} ]]; then + machineSystem="$(cat "${nixosSystem}"/system)" + else + machineSystem="$(nix --extra-experimental-features 'nix-command flakes' eval --raw "${flake}"#"${flakeAttr}".pkgs.system 2>/dev/null || echo "unknown")" + if [[ ${machineSystem} == "unknown" ]]; then + buildOn=auto + return + fi + fi + + if [[ ${system} == "${machineSystem}" ]]; then + buildOn=local + return + fi + + if [[ ${extraPlatforms} == "*${machineSystem}*" ]]; then + buildOn=local + return + fi + + local entropy + entropy="$(date +'%Y%m%d%H%M%S')" + if nix build \ + -L \ + "${nixOptions[@]}" \ + --expr \ + "derivation { system = \"$system\"; name = \"env-$entropy\"; builder = \"/bin/sh\"; args = [ \"-c\" \"echo > \$out\" ]; }"; then + # The local build failed + buildOn=local + fi + + buildOn=remote +} + +generateHardwareConfig() { + local maybeSudo="$maybeSudo" + mkdir -p "$(dirname "$hardwareConfigPath")" + case "$hardwareConfigBackend" in + nixos-facter) + if [[ ${isInstaller} == "y" ]]; then + if [[ ${hasNixOSFacter} == "n" ]]; then + abort "nixos-facter is not available in booted installer, use nixos-generate-config. For nixos-facter, you may want to boot an installer image from here instead: https://github.com/nix-community/nixos-images" + fi + else + maybeSudo="" + fi + + step "Generating hardware-configuration.nix using nixos-facter" + runSshNoTty -o ConnectTimeout=10 ${maybeSudo} "nixos-facter" >"$hardwareConfigPath" + ;; + nixos-generate-config) + step "Generating hardware-configuration.nix using nixos-generate-config" + runSshNoTty -o ConnectTimeout=10 nixos-generate-config --show-hardware-config --no-filesystems >"$hardwareConfigPath" + ;; + *) + abort "Unknown hardware config backend: $hardwareConfigBackend" + ;; + esac + + # to make sure nix knows about the new file + if command -v git >/dev/null; then + # handle relative paths + hardwareConfigPath="$(realpath "$hardwareConfigPath")" + pushd "$(dirname "$hardwareConfigPath")" + if git rev-parse --is-inside-work-tree >/dev/null; then + git add --intent-to-add --force -- "$hardwareConfigPath" + fi + popd + fi +} + +runKexec() { + if [[ ${isKexec} == "y" ]] || [[ ${isInstaller} == "y" ]]; then + return + fi + + if [[ ${isContainer} != "none" ]]; then + echo "WARNING: This script does not support running from a '${isContainer}' container. kexec will likely not work" >&2 + fi + + if [[ $kexecUrl == "" ]]; then + case "${isArch}" in + x86_64 | aarch64) + kexecUrl="https://github.com/nix-community/nixos-images/releases/download/nixos-24.11/nixos-kexec-installer-noninteractive-${isArch}-linux.tar.gz" + ;; + *) + abort "Unsupported architecture: ${isArch}. Our default kexec images only support x86_64 and aarch64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information." + ;; + esac + fi + + step Switching system into kexec + runSsh sh <<SSH +set -efu ${enableDebug} +$maybeSudo rm -rf /root/kexec +$maybeSudo mkdir -p /root/kexec +SSH + + # no way to reach global ipv4 destinations, use gh-v6.com automatically if github url + if [[ ${hasIpv6Only} == "y" ]] && [[ $kexecUrl == "https://github.com/"* ]]; then + kexecUrl=${kexecUrl/"github.com"/"gh-v6.com"} + fi + + if [[ -f $kexecUrl ]]; then + runSsh "${maybeSudo} tar -C /root/kexec -xvzf-" <"$kexecUrl" + elif [[ ${hasCurl} == "y" ]]; then + runSsh "curl --fail -Ss -L '${kexecUrl}' | ${maybeSudo} tar -C /root/kexec -xvzf-" + elif [[ ${hasWget} == "y" ]]; then + runSsh "wget '${kexecUrl}' -O- | ${maybeSudo} tar -C /root/kexec -xvzf-" + else + curl --fail -Ss -L "${kexecUrl}" | runSsh "${maybeSudo} tar -C /root/kexec -xvzf-" + fi + + runSsh <<SSH +TMPDIR=/root/kexec setsid ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags "${kexecExtraFlags}" +SSH + + # use the default SSH port to connect at this point + for i in "${!sshArgs[@]}"; do + if [[ ${sshArgs[i]} == "-p" ]]; then + sshArgs[i + 1]=$postKexecSshPort + break + fi + done + + # wait for machine to become unreachable. + while runSshTimeout -- exit 0; do sleep 1; done + + # After kexec we explicitly set the user to root@ + sshConnection="root@${sshHost}" + + # waiting for machine to become available again + until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done +} + +runDisko() { + local diskoScript=$1 + for path in "${!diskEncryptionKeys[@]}"; do + step "Uploading ${diskEncryptionKeys[$path]} to $path" + runSsh "umask 077; mkdir -p \"$(dirname "$path")\"; cat > $path" <"${diskEncryptionKeys[$path]}" + done + if [[ -n ${diskoScript} ]]; then + nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "$diskoScript" + elif [[ ${buildOn} == "remote" ]]; then + step Building disko script + # We need to do a nix copy first because nix build doesn't have --no-check-sigs + # Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359 + nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "${flake}#${flakeAttr}.system.build.${diskoMode}Script" \ + --derivation --no-check-sigs + # If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store` + diskoScript=$( + nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}" \ + --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$sshKeyDir%2Fnixos-anywhere&$sshStoreSettings" + ) + fi + + step Formatting hard drive with disko + runSsh "$diskoScript" +} + +nixosInstall() { + local nixosSystem=$1 + if [[ -n ${nixosSystem} ]]; then + step Uploading the system closure + nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "$nixosSystem" + elif [[ ${buildOn} == "remote" ]]; then + step Building the system closure + # We need to do a nix copy first because nix build doesn't have --no-check-sigs + # Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359 + nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "${flake}#${flakeAttr}.system.build.toplevel" \ + --derivation --no-check-sigs + # If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store` + nixosSystem=$( + nixBuild "${flake}#${flakeAttr}.system.build.toplevel" \ + --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$sshKeyDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" + ) + fi + + if [[ -n ${extraFiles} ]]; then + step Copying extra files + tar -C "$extraFiles" -cpf- . | runSsh "tar -C /mnt -xf- --no-same-owner" + + runSsh "chmod 755 /mnt" # tar also changes permissions of /mnt + fi + + if [[ ${#extraFilesOwnership[@]} -gt 0 ]]; then + # shellcheck disable=SC2016 + printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do chown -R "$ownership" "/mnt/$file"; done' + fi + + step Installing NixOS + runSsh sh <<SSH +set -eu ${enableDebug} +# when running not in nixos we might miss this directory, but it's needed in the nixos chroot during installation +export PATH="\$PATH:/run/current-system/sw/bin" + +# needed for installation if initrd-secrets are used +mkdir -p /mnt/tmp +chmod 777 /mnt/tmp +if [ ${copyHostKeys-n} = "y" ]; then + # NB we copy host keys that are in turn copied by kexec installer. + mkdir -m 755 -p /mnt/etc/ssh + for p in /etc/ssh/ssh_host_*; do + # Skip if the source file does not exist (i.e. glob did not match any files) + # or the destination already exists (e.g. copied with --extra-files). + if [ ! -e "\$p" ] || [ -e "/mnt/\$p" ]; then + continue + fi + cp -a "\$p" "/mnt/\$p" + done +fi +# https://stackoverflow.com/a/13864829 +if [ ! -z ${NIXOS_NO_CHECK+0} ]; then + export NIXOS_NO_CHECK +fi +nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem" +if [[ ${phases[reboot]} == 1 ]]; then + if command -v zpool >/dev/null && [ "\$(zpool list)" != "no pools available" ]; then + # we always want to export the zfs pools so people can boot from it without force import + umount -Rv /mnt/ + swapoff -a + zpool export -a || true + fi + nohup sh -c 'sleep 6 && reboot' >/dev/null & +fi +SSH + +} + +main() { + parseArgs "$@" + + if [[ ${vmTest} == y ]]; then + if [[ ${hardwareConfigBackend} != "none" ]]; then + abort "--vm-test is not supported with --generate-hardware-config. You need to generate the hardware configuration before you can run the VM test." >&2 + fi + runVmTest + exit 0 + fi + + if [[ ${buildOn} == "auto" ]]; then + checkBuildLocally + fi + + # parse flake nixos-install style syntax, get the system attr + if [[ -n ${flake} ]]; then + if [[ ${buildOn} == "local" ]] && [[ ${hardwareConfigBackend} == "none" ]]; then + if [[ ${phases[disko]} == 1 ]]; then + diskoScript=$(nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}") + fi + if [[ ${phases[install]} == 1 ]]; then + nixosSystem=$(nixBuild "${flake}#${flakeAttr}.system.build.toplevel") + fi + fi + elif [[ -n ${diskoScript} ]] && [[ -n ${nixosSystem} ]]; then + if [[ ! -e ${diskoScript} ]] || [[ ! -e ${nixosSystem} ]]; then + abort "${diskoScript} and ${nixosSystem} must be existing store-paths" + fi + else + abort "--flake or --store-paths must be set" + fi + + if [[ -n ${SSH_PRIVATE_KEY} ]] && [[ -z ${sshPrivateKeyFile} ]]; then + # $sshKeyDir is getting deleted on trap EXIT + sshPrivateKeyFile="$sshKeyDir/from-env" + ( + umask 077 + printf '%s\n' "$SSH_PRIVATE_KEY" >"$sshPrivateKeyFile" + ) + fi + + sshSettings=$(ssh "${sshArgs[@]}" -G "${sshConnection}") + sshUser=$(echo "$sshSettings" | awk '/^user / { print $2 }') + sshHost=$(echo "$sshSettings" | awk '/^hostname / { print $2 }') + + uploadSshKey + + importFacts + + if [[ ${hasTar-n} == "n" ]]; then + abort "no tar command found, but required to unpack kexec tarball" + fi + + if [[ ${hasCpio-n} == "n" ]]; then + abort "no cpio command found, but required to build the new initrd" + fi + + if [[ ${hasSetsid-n} == "n" ]]; then + abort "no setsid command found, but required to run the kexec script under a new session" + fi + + maybeSudo="" + if [[ ${hasSudo-n} == "y" ]]; then + maybeSudo="sudo" + elif [[ ${hasDoas-n} == "y" ]]; then + maybeSudo="doas" + fi + + if [[ ${isOs} != "Linux" ]]; then + abort "This script requires Linux as the operating system, but got $isOs" + fi + + if [[ ${phases[kexec]} == 1 ]]; then + runKexec + fi + + if [[ ${hardwareConfigBackend} != "none" ]]; then + generateHardwareConfig + fi + + # Before we do not have a valid hardware configuration we don't know the machine system + if [[ ${buildOn} == "auto" ]]; then + local remoteSystem + remoteSystem=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features nix-command config show system) + checkBuildLocally "${remoteSystem}" + # if we cannot figure it out at this point, we will build on the remote host + if [[ ${buildOn} == "auto" ]]; then + buildOn=remote + fi + fi + + if [[ ${buildOn} != "remote" ]] && [[ -n ${flake} ]] && [[ -z ${diskoScript} ]]; then + if [[ ${phases[disko]} == 1 ]]; then + diskoScript=$(nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}") + fi + if [[ ${phases[install]} == 1 ]]; then + nixosSystem=$(nixBuild "${flake}#${flakeAttr}.system.build.toplevel") + fi + fi + + # Installation will fail if non-root user is used for installer. + # Switch to root user by copying authorized_keys. + if [[ ${isInstaller} == "y" ]] && [[ ${sshUser} != "root" ]]; then + # Allow copy to fail if authorized_keys does not exist, like if using /etc/ssh/authorized_keys.d/ + runSsh "${maybeSudo} mkdir -p /root/.ssh; ${maybeSudo} cp ~/.ssh/authorized_keys /root/.ssh || true" + sshConnection="root@${sshHost}" + fi + + if [[ ${phases[disko]} == 1 ]]; then + runDisko "$diskoScript" + fi + + if [[ ${phases[install]} == 1 ]]; then + nixosInstall "$nixosSystem" + fi + + if [[ ${phases[reboot]} == 1 ]]; then + step Waiting for the machine to become unreachable due to reboot + while runSshTimeout -- exit 0; do sleep 1; done + fi + + step "Done!" +} + +main "$@" diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/README.md b/launch/.terraform/modules/pixelfed.deploy/terraform/README.md new file mode 100644 index 0000000..2d66b1a --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/README.md @@ -0,0 +1,21 @@ +# NixOS-Anywhere Terraform Modules Overview + +The nixos-Anywhere terraform modules allow you to use Terraform for installing +and updating NixOS. It simplifies the deployment process by integrating +nixos-anywhere functionality. + +Here's a brief overview of each module: + +- **[All-in-One](all-in-one.md)**: This is a consolidated module that first + installs NixOS using nixos-anywhere and then keeps it updated with + nixos-rebuild. If you choose this, you won't need additional deployment tools + like colmena. +- **[Install](install.md)**: This module focuses solely on installing NixOS via + nixos-anywhere. +- **[NixOS-Rebuild](nixos-rebuild.md)**: Use this module to remotely update an + existing NixOS machine using nixos-rebuild. +- **[Nix-Build](nix-build.md)**: This is a handy helper module designed to build + a flake attribute or an attribute from a nix file. + +For detailed information and usage examples, click on the respective module +links above. diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one.md b/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one.md new file mode 100644 index 0000000..c7279cf --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one.md @@ -0,0 +1,235 @@ +# All-in-one + +Combines the install and nixos-rebuild module in one interface to install NixOS +with nixos-anywhere and then keep it up-to-date with nixos-rebuild. + +## Example + +```hcl +locals { + ipv4 = "192.0.2.1" +} + +module "deploy" { + source = "github.com/nix-community/nixos-anywhere//terraform/all-in-one" + # with flakes + nixos_system_attr = ".#nixosConfigurations.mymachine.config.system.build.toplevel" + nixos_partitioner_attr = ".#nixosConfigurations.mymachine.config.system.build.diskoScript" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #nixos_system_attr = "config.system.build.toplevel" + #nixos_partitioner_attr = "config.system.build.diskoScript" + + target_host = local.ipv4 + # when instance id changes, it will trigger a reinstall + instance_id = local.ipv4 + # useful if something goes wrong + # debug_logging = true + # build the closure on the remote machine instead of locally + # build_on_remote = true + # script is below + extra_files_script = "${path.module}/decrypt-ssh-secrets.sh" + disk_encryption_key_scripts = [{ + path = "/tmp/secret.key" + # script is below + script = "${path.module}/decrypt-zfs-key.sh" + }] + # Optional, arguments passed to special_args here will be available from a NixOS module in this example the `terraform` argument: + # { terraform, ... }: { + # networking.interfaces.enp0s3.ipv4.addresses = [{ address = terraform.ip; prefixLength = 24; }]; + # } + # Note that this will means that your NixOS configuration will always depend on terraform! + # Skip to `Pass data persistently to the NixOS` for an alternative approach + #special_args = { + # terraform = { + # ip = "192.0.2.0" + # } + #} +} +``` + +_Note:_ You need to mark scripts as executable (`chmod +x`) + +### ./decrypt-ssh-secrets.sh + +```bash +#!/usr/bin/env bash + +mkdir -p etc/ssh var/lib/secrets + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +umask 0177 +sops --extract '["initrd_ssh_key"]' --decrypt "$SCRIPT_DIR/secrets.yaml" >./var/lib/secrets/initrd_ssh_key + +# restore umask +umask 0022 + +for keyname in ssh_host_rsa_key ssh_host_rsa_key.pub ssh_host_ed25519_key ssh_host_ed25519_key.pub; do + if [[ $keyname == *.pub ]]; then + umask 0133 + else + umask 0177 + fi + sops --extract '["'$keyname'"]' --decrypt "$SCRIPT_DIR/secrets.yaml" >"./etc/ssh/$keyname" +done +``` + +### ./decrypt-zfs-key.sh + +```bash +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +cd "$SCRIPT_DIR" +sops --extract '["zfs-key"]' --decrypt "$SCRIPT_DIR/secrets.yaml" +``` + +## See also + +- [nixos-wiki setup](https://github.com/NixOS/nixos-wiki-infra/blob/main/terraform/nixos-wiki/main.tf) + for hetzner-cloud + +## Pass data persistently to the NixOS + +This guide outlines how to pass data from Terraform to NixOS by generating a +file during Terraform execution and including it in your NixOS configuration. +This approach works well if your Terraform and NixOS configurations are stored +in the same Git repository. + +### Why Use This Method? + +This method provides a straightforward way to transfer values from Terraform to +NixOS without relying on special_args. + +- **Advantages**: + - You can continue to use nix build or nixos-rebuild to evaluate your + configuration without interruption. Simplifies configuration management by + centralizing state in a single repository. +- **Disadvantages**: + - Deploying new machines requires tracking additional state. Every time + Terraform updates the JSON file, you'll need to commit these changes to your + repository. + +### Implementation + +Add the following snippet to your Terraform configuration to create and manage a +JSON file containing the necessary variables for NixOS. This file will be +automatically added to your Git repository, ensuring the data persists. + +Assuming you have your terraform and nixos configuration in the same git +repository. You can use the following snippet to `git add` a file generated by +`terraform` during execution to pass data from terraform to NixOS. These changes +should be committed afterwards. This is an alternative over using +`special_args`. Advantage: you can still use nix build or nixos-rebuild on your +flake to evaluate your configuration. Disadvantage: Deploying new machines also +means you need to track additional state and make additional commits whenever +terraform updates the json file. + +```hcl +locals { + nixos_vars_file = "nixos-vars.json" # Path to the JSON file containing NixOS variables + nixos_vars = { + ip = "192.0.2.0" # Replace with actual variables + } +} +resource "local_file" "nixos_vars" { + content = jsonencode(local.nixos_vars) # Converts variables to JSON + filename = local.nixos_vars_file # Specifies the output file path + file_permission = "600" + + # Automatically adds the generated file to Git + provisioner "local-exec" { + interpreter = ["bash", "-c"] + command = "git add -f '${local.nixos_vars_file}'" + } +} +``` + +After applying the Terraform changes, ensure you commit the updated +`nixos-vars.json` file to your Git repository: + +```bash +git commit -m "Update NixOS variables from Terraform" +``` + +You can import this json file into your configuration like this: + +```nix +let + nixosVars = builtins.fromJSON (builtins.readFile ./nixos-vars.json); +in +{ + # Example usage of imported variables + networking.hostName = "example-machine"; + networking.interfaces.eth0.ipv4.addresses = [ + { + address = nixosVars.ip; # Use the IP from nixos-vars.json + prefixLength = 24; + } + ]; +} +``` + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +| -------------------------------------------------------------------------------------- | ---------------- | ------- | +| <a name="module_install"></a> [install](#module_install) | ../install | n/a | +| <a name="module_nixos-rebuild"></a> [nixos-rebuild](#module_nixos-rebuild) | ../nixos-rebuild | n/a | +| <a name="module_partitioner-build"></a> [partitioner-build](#module_partitioner-build) | ../nix-build | n/a | +| <a name="module_system-build"></a> [system-build](#module_system-build) | ../nix-build | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | :------: | +| <a name="input_debug_logging"></a> [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no | +| <a name="input_deployment_ssh_key"></a> [deployment\_ssh\_key](#input_deployment_ssh_key) | Content of private key used to deploy to the target\_host after initial installation. To ensure maximum security, it is advisable to connect to your host using ssh-agent instead of relying on this variable | `string` | `null` | no | +| <a name="input_disk_encryption_key_scripts"></a> [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system | <pre>list(object({<br> path = string<br> script = string<br> }))</pre> | `[]` | no | +| <a name="input_extra_environment"></a> [extra\_environment](#input_extra_environment) | Extra environment variables to be set during installation. This can be useful to set extra variables for the extra\_files\_script or disk\_encryption\_key\_scripts | `map(string)` | `{}` | no | +| <a name="input_extra_files_script"></a> [extra\_files\_script](#input_extra_files_script) | A script that should place files in the current directory that will be copied to the targets / directory | `string` | `null` | no | +| <a name="input_file"></a> [file](#input_file) | Nix file containing the nixos\_system\_attr and nixos\_partitioner\_attr. Use this if you are not using flake | `string` | `null` | no | +| <a name="input_install_port"></a> [install\_port](#input_install_port) | SSH port used to connect to the target\_host, before installing NixOS. If null than the value of `target_port` is used | `string` | `null` | no | +| <a name="input_install_ssh_key"></a> [install\_ssh\_key](#input_install_ssh_key) | Content of private key used to connect to the target\_host during initial installation | `string` | `null` | no | +| <a name="input_install_user"></a> [install\_user](#input_install_user) | SSH user used to connect to the target\_host, before installing NixOS. If null than the value of `target_host` is used | `string` | `null` | no | +| <a name="input_instance_id"></a> [instance\_id](#input_instance_id) | The instance id of the target\_host, used to track when to reinstall the machine | `string` | `null` | no | +| <a name="input_kexec_tarball_url"></a> [kexec\_tarball\_url](#input_kexec_tarball_url) | NixOS kexec installer tarball url | `string` | `null` | no | +| <a name="input_nix_options"></a> [nix\_options](#input_nix_options) | the options of nix | `map(string)` | `{}` | no | +| <a name="input_nixos_facter_path"></a> [nixos\_facter\_path](#input_nixos_facter_path) | Path to which to write a `facter.json` generated by `nixos-facter`. | `string` | `""` | no | +| <a name="input_nixos_generate_config_path"></a> [nixos\_generate\_config\_path](#input_nixos_generate_config_path) | Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`. | `string` | `""` | no | +| <a name="input_nixos_partitioner_attr"></a> [nixos\_partitioner\_attr](#input_nixos_partitioner_attr) | Nixos partitioner and mount script i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.diskoNoDeps or just your-evaluated.config.system.build.diskNoDeps. `config.system.build.diskNoDeps` is provided by the disko nixos module | `string` | n/a | yes | +| <a name="input_nixos_system_attr"></a> [nixos\_system\_attr](#input_nixos_system_attr) | The nixos system to deploy i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.toplevel or just your-evaluated-nixos.config.system.build.toplevel if you are not using flakes | `string` | n/a | yes | +| <a name="input_no_reboot"></a> [no\_reboot](#input_no_reboot) | DEPRECATED: Use `phases` instead. Do not reboot after installation | `bool` | `false` | no | +| <a name="input_phases"></a> [phases](#input_phases) | Phases to run. See `nixos-anywhere --help` for more information | `set(string)` | <pre>[<br> "kexec",<br> "disko",<br> "install",<br> "reboot"<br>]</pre> | no | +| <a name="input_special_args"></a> [special\_args](#input_special_args) | A map exposed as NixOS's `specialArgs` thru a file. | `any` | `{}` | no | +| <a name="input_build_on_remote"></a> [build\_on\_remote](#input_build_on_remote) | Build the closure on the remote machine instead of building it locally and copying it over | `bool` | `false` | no | +| <a name="input_stop_after_disko"></a> [stop\_after\_disko](#input_stop_after_disko) | DEPRECATED: Use `phases` instead. Exit after disko formatting | `bool` | `false` | no | +| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | +| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host after installing NixOS. If install\_port is not set than this port is also used before installing. | `number` | `22` | no | +| <a name="input_target_user"></a> [target\_user](#input_target_user) | SSH user used to connect to the target\_host after installing NixOS. If install\_user is not set than this user is also used before installing. | `string` | `"root"` | no | + +## Outputs + +| Name | Description | +| ----------------------------------------------------- | ----------- | +| <a name="output_result"></a> [result](#output_result) | n/a | + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/main.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/main.tf new file mode 100644 index 0000000..fa5d7eb --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/main.tf @@ -0,0 +1,64 @@ +module "system-build" { + source = "../nix-build" + attribute = var.nixos_system_attr + file = var.file + nix_options = var.nix_options + special_args = var.special_args +} + +module "partitioner-build" { + source = "../nix-build" + attribute = var.nixos_partitioner_attr + file = var.file + nix_options = var.nix_options + special_args = var.special_args +} + +locals { + install_user = var.install_user == null ? var.target_user : var.install_user + install_port = var.install_port == null ? var.target_port : var.install_port +} + +module "install" { + source = "../install" + kexec_tarball_url = var.kexec_tarball_url + target_user = local.install_user + target_host = var.target_host + target_port = local.install_port + nixos_partitioner = module.partitioner-build.result.out + nixos_system = module.system-build.result.out + ssh_private_key = var.install_ssh_key + debug_logging = var.debug_logging + extra_files_script = var.extra_files_script + disk_encryption_key_scripts = var.disk_encryption_key_scripts + extra_environment = var.extra_environment + instance_id = var.instance_id + phases = var.phases + nixos_generate_config_path = var.nixos_generate_config_path + nixos_facter_path = var.nixos_facter_path + build_on_remote = var.build_on_remote + # deprecated attributes + stop_after_disko = var.stop_after_disko + no_reboot = var.no_reboot +} + +module "nixos-rebuild" { + depends_on = [ + module.install + ] + + # Do not execute this step if var.stop_after_disko == true + count = var.stop_after_disko ? 0 : 1 + + source = "../nixos-rebuild" + nixos_system = module.system-build.result.out + ssh_private_key = var.deployment_ssh_key + target_host = var.target_host + target_user = var.target_user + target_port = var.target_port + install_bootloader = var.install_bootloader +} + +output "result" { + value = module.system-build.result +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/variables.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/variables.tf new file mode 100644 index 0000000..3ca2792 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/all-in-one/variables.tf @@ -0,0 +1,151 @@ +variable "kexec_tarball_url" { + type = string + description = "NixOS kexec installer tarball url" + default = null +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_partitioner_attr" { + type = string + description = "Nixos partitioner and mount script i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.diskoNoDeps or just your-evaluated.config.system.build.diskNoDeps. `config.system.build.diskNoDeps` is provided by the disko nixos module" +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_system_attr" { + type = string + description = "The nixos system to deploy i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.toplevel or just your-evaluated-nixos.config.system.build.toplevel if you are not using flakes" +} + +variable "file" { + type = string + description = "Nix file containing the nixos_system_attr and nixos_partitioner_attr. Use this if you are not using flake" + default = null +} + +variable "target_host" { + type = string + description = "DNS host to deploy to" +} + +variable "install_user" { + type = string + description = "SSH user used to connect to the target_host, before installing NixOS. If null than the value of `target_host` is used" + default = null +} + +variable "install_port" { + type = string + description = "SSH port used to connect to the target_host, before installing NixOS. If null than the value of `target_port` is used" + default = null +} + +variable "target_user" { + type = string + description = "SSH user used to connect to the target_host after installing NixOS. If install_user is not set than this user is also used before installing." + default = "root" +} + +variable "target_port" { + type = number + description = "SSH port used to connect to the target_host after installing NixOS. If install_port is not set than this port is also used before installing." + default = 22 +} + +variable "instance_id" { + type = string + description = "The instance id of the target_host, used to track when to reinstall the machine" + default = null +} + +variable "install_ssh_key" { + type = string + description = "Content of private key used to connect to the target_host during initial installation" + default = null +} + +variable "deployment_ssh_key" { + type = string + description = "Content of private key used to deploy to the target_host after initial installation. To ensure maximum security, it is advisable to connect to your host using ssh-agent instead of relying on this variable" + default = null +} + +variable "debug_logging" { + type = bool + description = "Enable debug logging" + default = false +} + +variable "stop_after_disko" { + type = bool + description = "DEPRECATED: Use `phases` instead. Exit after disko formatting" + default = false +} + +variable "no_reboot" { + type = bool + description = "DEPRECATED: Use `phases` instead. Do not reboot after installation" + default = false +} + +variable "phases" { + type = set(string) + description = "Phases to run. See `nixos-anywhere --help` for more information" + default = ["kexec", "disko", "install", "reboot"] +} + +variable "extra_files_script" { + type = string + description = "A script that should place files in the current directory that will be copied to the targets / directory" + default = null +} + +variable "disk_encryption_key_scripts" { + type = list(object({ + path = string + script = string + })) + description = "Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system" + default = [] +} + +variable "extra_environment" { + type = map(string) + description = "Extra environment variables to be set during installation. This can be useful to set extra variables for the extra_files_script or disk_encryption_key_scripts" + default = {} +} + +variable "nix_options" { + type = map(string) + description = "the options of nix" + default = {} +} + +variable "nixos_generate_config_path" { + type = string + description = "Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`." + default = "" +} + +variable "nixos_facter_path" { + type = string + description = "Path to which to write a `facter.json` generated by `nixos-facter`." + default = "" +} + +variable "special_args" { + type = any + default = {} + description = "A map exposed as NixOS's `specialArgs` thru a file." +} + +variable "build_on_remote" { + type = bool + description = "Build the closure on the remote machine instead of building it locally and copying it over" + default = false +} + +variable "install_bootloader" { + type = bool + description = "Install/re-install the bootloader" + default = false +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/install.md b/launch/.terraform/modules/pixelfed.deploy/terraform/install.md new file mode 100644 index 0000000..eb3439f --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/install.md @@ -0,0 +1,91 @@ +# Install + +Install NixOS with nixos-anywhere + +## Example + +```hcl +locals { + ipv4 = "192.0.2.1" +} + +module "system-build" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + # with flakes + attribute = ".#nixosConfigurations.mymachine.config.system.build.toplevel" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #attribute = "config.system.build.toplevel" +} + +module "disko" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + # with flakes + attribute = ".#nixosConfigurations.mymachine.config.system.build.diskoScript" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #attribute = "config.system.build.diskoScript" +} + +module "install" { + source = "github.com/nix-community/nixos-anywhere//terraform/install" + nixos_system = module.system-build.result.out + nixos_partitioner = module.disko.result.out + target_host = local.ipv4 +} +``` + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +| --------------------------------------------------- | ------- | +| <a name="provider_null"></a> [null](#provider_null) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ------------------------------------------------------------------------------------------------------------------- | -------- | +| [null_resource.nixos-remote](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | :------: | +| <a name="input_build_on_remote"></a> [build\_on\_remote](#input_build_on_remote) | Build the closure on the remote machine instead of building it locally and copying it over | `bool` | `false` | no | +| <a name="input_debug_logging"></a> [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no | +| <a name="input_disk_encryption_key_scripts"></a> [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system | <pre>list(object({<br> path = string<br> script = string<br> }))</pre> | `[]` | no | +| <a name="input_extra_environment"></a> [extra\_environment](#input_extra_environment) | Extra environment variables to be set during installation. This can be useful to set extra variables for the extra\_files\_script or disk\_encryption\_key\_scripts | `map(string)` | `{}` | no | +| <a name="input_extra_files_script"></a> [extra\_files\_script](#input_extra_files_script) | A script that should place files in the current directory that will be copied to the targets / directory | `string` | `null` | no | +| <a name="input_flake"></a> [flake](#input_flake) | The flake to install the system from | `string` | `""` | no | +| <a name="input_instance_id"></a> [instance\_id](#input_instance_id) | The instance id of the target\_host, used to track when to reinstall the machine | `string` | `null` | no | +| <a name="input_kexec_tarball_url"></a> [kexec\_tarball\_url](#input_kexec_tarball_url) | NixOS kexec installer tarball url | `string` | `null` | no | +| <a name="input_nixos_facter_path"></a> [nixos\_facter\_path](#input_nixos_facter_path) | Path to which to write a `facter.json` generated by `nixos-facter`. This option cannot be set at the same time as `nixos_generate_config_path`. | `string` | `""` | no | +| <a name="input_nixos_generate_config_path"></a> [nixos\_generate\_config\_path](#input_nixos_generate_config_path) | Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`. This option cannot be set at the same time as `nixos_facter_path`. | `string` | `""` | no | +| <a name="input_nixos_partitioner"></a> [nixos\_partitioner](#input_nixos_partitioner) | nixos partitioner and mount script | `string` | `""` | no | +| <a name="input_nixos_system"></a> [nixos\_system](#input_nixos_system) | The nixos system to deploy | `string` | `""` | no | +| <a name="input_no_reboot"></a> [no\_reboot](#input_no_reboot) | DEPRECATED: Use `phases` instead. Do not reboot after installation | `bool` | `false` | no | +| <a name="input_phases"></a> [phases](#input_phases) | Phases to run. See `nixos-anywhere --help` for more information | `list(string)` | <pre>[<br> "kexec",<br> "disko",<br> "install",<br> "reboot"<br>]</pre> | no | +| <a name="input_ssh_private_key"></a> [ssh\_private\_key](#input_ssh_private_key) | Content of private key used to connect to the target\_host | `string` | `""` | no | +| <a name="input_stop_after_disko"></a> [stop\_after\_disko](#input_stop_after_disko) | DEPRECATED: Use `phases` instead. Exit after disko formatting | `bool` | `false` | no | +| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | +| <a name="input_target_pass"></a> [target\_pass](#input_target_pass) | Password used to connect to the target\_host | `string` | `null` | no | +| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host | `number` | `22` | no | +| <a name="input_target_user"></a> [target\_user](#input_target_user) | SSH user used to connect to the target\_host | `string` | `"root"` | no | + +## Outputs + +No outputs. + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/install/main.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/install/main.tf new file mode 100644 index 0000000..9f1816a --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/install/main.tf @@ -0,0 +1,35 @@ +locals { + disk_encryption_key_scripts = [for k in var.disk_encryption_key_scripts : "\"${k.path}\" \"${k.script}\""] + removed_phases = setunion(var.stop_after_disko ? ["install"] : [], (var.no_reboot ? ["reboot"] : [])) + phases = setsubtract(var.phases, local.removed_phases) + arguments = jsonencode({ + ssh_private_key = var.ssh_private_key + debug_logging = var.debug_logging + kexec_tarball_url = var.kexec_tarball_url + nixos_partitioner = var.nixos_partitioner + nixos_system = var.nixos_system + target_user = var.target_user + target_host = var.target_host + target_port = var.target_port + target_pass = var.target_pass + extra_files_script = var.extra_files_script + build_on_remote = var.build_on_remote + flake = var.flake + phases = join(",", local.phases) + nixos_generate_config_path = var.nixos_generate_config_path + nixos_facter_path = var.nixos_facter_path + }) +} + +resource "null_resource" "nixos-remote" { + triggers = { + instance_id = var.instance_id + } + provisioner "local-exec" { + environment = merge({ + ARGUMENTS = local.arguments + }, var.extra_environment) + command = "${path.module}/run-nixos-anywhere.sh ${join(" ", local.disk_encryption_key_scripts)}" + quiet = var.debug_logging + } +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/install/providers.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/install/providers.tf new file mode 100644 index 0000000..c3e00a5 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/install/providers.tf @@ -0,0 +1,5 @@ +terraform { + required_providers { + null = { source = "hashicorp/null" } + } +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/install/run-nixos-anywhere.sh b/launch/.terraform/modules/pixelfed.deploy/terraform/install/run-nixos-anywhere.sh new file mode 100755 index 0000000..1d259a1 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/install/run-nixos-anywhere.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +declare -A input + +while IFS= read -r -d '' key && IFS= read -r -d '' value; do + input[$key]=$value +done < <(jq -j 'to_entries[] | (.key, "\u0000", .value, "\u0000")' <<<"${ARGUMENTS}") + +args=() + +if [[ ${input[debug_logging]} == "true" ]]; then + set -x + declare -p input + args+=("--debug") +fi +if [[ ${input[kexec_tarball_url]} != "null" ]]; then + args+=("--kexec" "${input[kexec_tarball_url]}") +fi +if [[ ${input[build_on_remote]} == "true" ]]; then + args+=("--build-on-remote") +fi +if [[ -n ${input[flake]} ]]; then + args+=("--flake" "${input[flake]}") +else + args+=("--store-paths" "${input[nixos_partitioner]}" "${input[nixos_system]}") +fi +if [[ -n ${input[nixos_generate_config_path]} ]]; then + if [[ -n ${input[nixos_facter_path]} ]]; then + echo "cannot set both variables 'nixos_generate_config_path' and 'nixos_facter_path'!" >&2 + exit 1 + fi + args+=("--generate-hardware-config" "nixos-generate-config" "${input[nixos_generate_config_path]}") +elif [[ -n ${input[nixos_facter_path]} ]]; then + args+=("--generate-hardware-config" "nixos-facter" "${input[nixos_facter_path]}") +fi +args+=(--phases "${input[phases]}") +if [[ ${input[ssh_private_key]} != null ]]; then + export SSH_PRIVATE_KEY="${input[ssh_private_key]}" +fi +if [[ ${input[target_pass]} != null ]]; then + export SSHPASS=${input[target_pass]} + args+=("--env-password") +fi + +tmpdir=$(mktemp -d) +cleanup() { + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +if [[ ${input[extra_files_script]} != "null" ]]; then + if [[ ! -f ${input[extra_files_script]} ]]; then + echo "extra_files_script '${input[extra_files_script]}' does not exist" + exit 1 + fi + if [[ ! -x ${input[extra_files_script]} ]]; then + echo "extra_files_script '${input[extra_files_script]}' is not executable" + exit 1 + fi + extra_files_script=$(realpath "${input[extra_files_script]}") + mkdir "${tmpdir}/extra-files" + pushd "${tmpdir}/extra-files" + $extra_files_script + popd + args+=("--extra-files" "${tmpdir}/extra-files") +fi + +args+=("-p" "${input[target_port]}") +args+=("${input[target_user]}@${input[target_host]}") + +keyIdx=0 +while [[ $# -gt 0 ]]; do + if [[ ! -f $2 ]]; then + echo "Script file '$2' does not exist" + exit 1 + fi + if [[ ! -x $2 ]]; then + echo "Script file '$2' is not executable" + exit 1 + fi + mkdir -p "${tmpdir}/keys" + "$2" >"${tmpdir}/keys/$keyIdx" + args+=("--disk-encryption-keys" "$1" "${tmpdir}/keys/$keyIdx") + shift + shift + keyIdx=$((keyIdx + 1)) +done + +nix run --extra-experimental-features 'nix-command flakes' "path:${SCRIPT_DIR}/../..#nixos-anywhere" -- "${args[@]}" diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/install/variables.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/install/variables.tf new file mode 100644 index 0000000..cd07bf7 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/install/variables.tf @@ -0,0 +1,123 @@ +variable "kexec_tarball_url" { + type = string + description = "NixOS kexec installer tarball url" + default = null +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_partitioner" { + type = string + description = "nixos partitioner and mount script" + default = "" +} + +# To make this re-usable we maybe should accept a store path here? +variable "nixos_system" { + type = string + description = "The nixos system to deploy" + default = "" +} + +variable "target_host" { + type = string + description = "DNS host to deploy to" +} + +variable "target_user" { + type = string + description = "SSH user used to connect to the target_host" + default = "root" +} + +variable "target_port" { + type = number + description = "SSH port used to connect to the target_host" + default = 22 +} + +variable "target_pass" { + type = string + description = "Password used to connect to the target_host" + default = null +} + +variable "ssh_private_key" { + type = string + description = "Content of private key used to connect to the target_host" + default = "" +} + +variable "instance_id" { + type = string + description = "The instance id of the target_host, used to track when to reinstall the machine" + default = null +} + +variable "debug_logging" { + type = bool + description = "Enable debug logging" + default = false +} + +variable "extra_files_script" { + type = string + description = "A script that should place files in the current directory that will be copied to the targets / directory" + default = null +} + +variable "disk_encryption_key_scripts" { + type = list(object({ + path = string + script = string + })) + description = "Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system" + default = [] +} + +variable "extra_environment" { + type = map(string) + description = "Extra environment variables to be set during installation. This can be useful to set extra variables for the extra_files_script or disk_encryption_key_scripts" + default = {} +} + +variable "stop_after_disko" { + type = bool + description = "DEPRECATED: Use `phases` instead. Exit after disko formatting" + default = false +} + +variable "no_reboot" { + type = bool + description = "DEPRECATED: Use `phases` instead. Do not reboot after installation" + default = false +} + +variable "phases" { + type = list(string) + description = "Phases to run. See `nixos-anywhere --help` for more information" + default = ["kexec", "disko", "install", "reboot"] +} + +variable "build_on_remote" { + type = bool + description = "Build the closure on the remote machine instead of building it locally and copying it over" + default = false +} + +variable "flake" { + type = string + description = "The flake to install the system from" + default = "" +} + +variable "nixos_generate_config_path" { + type = string + description = "Path to which to write a `hardware-configuration.nix` generated by `nixos-generate-config`. This option cannot be set at the same time as `nixos_facter_path`." + default = "" +} + +variable "nixos_facter_path" { + type = string + description = "Path to which to write a `facter.json` generated by `nixos-facter`. This option cannot be set at the same time as `nixos_generate_config_path`." + default = "" +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build.md b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build.md new file mode 100644 index 0000000..fe63af1 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build.md @@ -0,0 +1,47 @@ +# Nix-build + +Small helper module to run do build a flake attribute or attribute from a nix +file. + +## Example + +- See [install](install.md) or [nixos-rebuild](nixos-rebuild.md) + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +| --------------------------------------------------------------- | ------- | +| <a name="provider_external"></a> [external](#provider_external) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| --------------------------------------------------------------------------------------------------------------------------- | ----------- | +| [external_external.nix-build](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---------------------------------------------------------------------- | --------------------------------------------------- | ------------- | ------- | :------: | +| <a name="input_attribute"></a> [attribute](#input_attribute) | the attribute to build, can also be a flake | `string` | n/a | yes | +| <a name="input_file"></a> [file](#input_file) | the nix file to evaluate, if not run in flake mode | `string` | `null` | no | +| <a name="input_nix_options"></a> [nix\_options](#input_nix_options) | the options of nix | `map(string)` | `{}` | no | +| <a name="input_special_args"></a> [special\_args](#input_special_args) | A map exposed as NixOS's `specialArgs` thru a file. | `any` | `{}` | no | + +## Outputs + +| Name | Description | +| ----------------------------------------------------- | ----------- | +| <a name="output_result"></a> [result](#output_result) | n/a | + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/main.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/main.tf new file mode 100644 index 0000000..7a3c335 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/main.tf @@ -0,0 +1,17 @@ +locals { + nix_options = jsonencode({ + options = { for k, v in var.nix_options : k => v } + }) +} +data "external" "nix-build" { + program = [ "${path.module}/nix-build.sh" ] + query = { + attribute = var.attribute + file = var.file + nix_options = local.nix_options + special_args = jsonencode(var.special_args) + } +} +output "result" { + value = data.external.nix-build.result +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/nix-build.sh b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/nix-build.sh new file mode 100755 index 0000000..0e22357 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/nix-build.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -efu + +declare file attribute nix_options special_args +eval "$(jq -r '@sh "attribute=\(.attribute) file=\(.file) nix_options=\(.nix_options) special_args=\(.special_args)"')" +if [ "${nix_options}" != '{"options":{}}' ]; then + options=$(echo "${nix_options}" | jq -r '.options | to_entries | map("--option \(.key) \(.value)") | join(" ")') +else + options="" +fi +if [[ ${special_args-} == "{}" ]]; then + # no special arguments, proceed as normal + if [[ -n ${file-} ]] && [[ -e ${file-} ]]; then + # shellcheck disable=SC2086 + out=$(nix build --no-link --json $options -f "$file" "$attribute") + else + # shellcheck disable=SC2086 + out=$(nix build --no-link --json ${options} "$attribute") + fi +else + if [[ ${file-} != 'null' ]]; then + echo "special_args are currently only supported when using flakes!" >&2 + exit 1 + fi + # pass the args in a pure fashion by extending the original config + rest="$(echo "${attribute}" | cut -d "#" -f 2)" + # e.g. config_path=nixosConfigurations.aarch64-linux.myconfig + config_path="${rest%.config.*}" + # e.g. config_attribute=config.system.build.toplevel + config_attribute="config.${rest#*.config.}" + + # grab flake nar from error message + flake_rel="$(echo "${attribute}" | cut -d "#" -f 1)" + # e.g. flake_rel="." + flake_dir="$(readlink -f "${flake_rel}")" + flake_path="${flake_dir}/flake.nix" + flake_json="$(nix flake prefetch "${flake_dir}" --json)" + flake_nar="$(echo "$flake_json" | jq -r '.hash')" + store_path="$(echo "${flake_json}" | jq -r '.storePath')" + # while we have a store path now, for a repo this reflects its root level, + # so search for the largest child segment yielding a match in that store dir. + iter_path="${flake_path}" + while [[ ${iter_path} != "/" ]]; do + parent="$(dirname "${iter_path}")" + child_segment="${flake_path//$parent/}" + if [[ -f "${store_path}${child_segment}" ]]; then + target_segment="${child_segment}" + fi + iter_path="${parent}" + done + # substitute variables into the template + nix_expr="(builtins.getFlake ''file://${flake_dir}?dir=${target_segment//\/flake.nix/}&narHash=${flake_nar}'').${config_path}.extendModules { specialArgs = builtins.fromJSON ''${special_args}''; }" + # inject `special_args` into nixos config's `specialArgs` + # shellcheck disable=SC2086 + out=$(nix build --no-link --json ${options} --expr "${nix_expr}" "${config_attribute}") +fi +printf '%s' "$out" | jq -c '.[].outputs' diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/variables.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/variables.tf new file mode 100644 index 0000000..bad23f7 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nix-build/variables.tf @@ -0,0 +1,22 @@ +variable "attribute" { + type = string + description = "the attribute to build, can also be a flake" +} + +variable "file" { + type = string + description = "the nix file to evaluate, if not run in flake mode" + default = null +} + +variable "nix_options" { + type = map(string) + description = "the options of nix" + default = {} +} + +variable "special_args" { + type = any + default = {} + description = "A map exposed as NixOS's `specialArgs` thru a file." +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild.md b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild.md new file mode 100644 index 0000000..0be26bb --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild.md @@ -0,0 +1,66 @@ +# Nixos-rebuild + +Update NixOS machine with nixos-rebuild on a remote machine + +## Example + +```hcl +locals { + ipv4 = "192.0.2.1" +} + +module "system-build" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + # with flakes + attribute = ".#nixosConfigurations.mymachine.config.system.build.toplevel" + # without flakes + # file can use (pkgs.nixos []) function from nixpkgs + #file = "${path.module}/../.." + #attribute = "config.system.build.toplevel" +} + +module "deploy" { + source = "github.com/nix-community/nixos-anywhere//terraform/nixos-rebuild" + nixos_system = module.system-build.result.out + target_host = local.ipv4 +} +``` + +<!-- BEGIN_TF_DOCS --> + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +| --------------------------------------------------- | ------- | +| <a name="provider_null"></a> [null](#provider_null) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| -------------------------------------------------------------------------------------------------------------------- | -------- | +| [null_resource.nixos-rebuild](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | -------- | :------: | +| <a name="input_ignore_systemd_errors"></a> [ignore\_systemd\_errors](#input_ignore_systemd_errors) | Ignore systemd errors happening during deploy | `bool` | `false` | no | +| <a name="input_nixos_system"></a> [nixos\_system](#input_nixos_system) | The nixos system to deploy | `string` | n/a | yes | +| <a name="input_ssh_private_key"></a> [ssh\_private\_key](#input_ssh_private_key) | Content of private key used to connect to the target\_host. If set to - no key is passed to openssh and ssh will use its own configuration | `string` | `"-"` | no | +| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | +| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host | `number` | `22` | no | +| <a name="input_target_user"></a> [target\_user](#input_target_user) | User to deploy as | `string` | `"root"` | no | + +## Outputs + +No outputs. + +<!-- END_TF_DOCS --> diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/deploy.sh b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/deploy.sh new file mode 100755 index 0000000..61da6e1 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/deploy.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +set -uex -o pipefail + +if [ "$#" -ne 6 ]; then + echo "USAGE: $0 NIXOS_SYSTEM TARGET_USER TARGET_HOST TARGET_PORT IGNORE_SYSTEMD_ERRORS INSTALL_BOOTLOADER" >&2 + exit 1 +fi + +NIXOS_SYSTEM=$1 +TARGET_USER=$2 +TARGET_HOST=$3 +TARGET_PORT=$4 +IGNORE_SYSTEMD_ERRORS=$5 +INSTALL_BOOTLOADER=$6 + +shift 6 + +TARGET="${TARGET_USER}@${TARGET_HOST}" + +workDir=$(mktemp -d) +trap 'rm -rf "$workDir"' EXIT + +sshOpts=(-p "${TARGET_PORT}") +sshOpts+=(-o UserKnownHostsFile=/dev/null) +sshOpts+=(-o StrictHostKeyChecking=no) + +set +x +if [[ -n ${SSH_KEY+x} && ${SSH_KEY} != "-" ]]; then + sshPrivateKeyFile="$workDir/ssh_key" + # Create the file with 0700 - umask calculation: 777 - 700 = 077 + ( + umask 077 + echo "$SSH_KEY" >"$sshPrivateKeyFile" + ) + unset SSH_AUTH_SOCK # don't use system agent if key was supplied + sshOpts+=(-o "IdentityFile=${sshPrivateKeyFile}") +fi +set -x + +try=1 +until NIX_SSHOPTS="${sshOpts[*]}" nix copy -s --experimental-features nix-command --to "ssh://$TARGET" "$NIXOS_SYSTEM"; do + if [[ $try -gt 10 ]]; then + echo "retries exhausted" >&2 + exit 1 + fi + sleep 10 + try=$((try + 1)) +done + +if [[ $INSTALL_BOOTLOADER == "true" ]]; then + extra_env='NIXOS_INSTALL_BOOTLOADER=1' +else + extra_env='' +fi + +switchCommand="nix-env -p /nix/var/nix/profiles/system --set $(printf "%q" "$NIXOS_SYSTEM"); $extra_env /nix/var/nix/profiles/system/bin/switch-to-configuration switch" + +if [[ $TARGET_USER != "root" ]]; then + switchCommand="sudo bash -c '$switchCommand'" +fi +deploy_status=0 +# shellcheck disable=SC2029 +ssh "${sshOpts[@]}" "$TARGET" "$switchCommand" || deploy_status="$?" +if [[ $IGNORE_SYSTEMD_ERRORS == "true" && $deploy_status == "4" ]]; then + exit 0 +fi +exit "$deploy_status" diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/main.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/main.tf new file mode 100644 index 0000000..84461c8 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/main.tf @@ -0,0 +1,11 @@ +resource "null_resource" "nixos-rebuild" { + triggers = { + store_path = var.nixos_system + } + provisioner "local-exec" { + environment = { + SSH_KEY = var.ssh_private_key + } + command = "${path.module}/deploy.sh ${var.nixos_system} ${var.target_user} ${var.target_host} ${var.target_port} ${var.ignore_systemd_errors} ${var.install_bootloader}" + } +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/variables.tf b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/variables.tf new file mode 100644 index 0000000..90e0b0c --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/nixos-rebuild/variables.tf @@ -0,0 +1,39 @@ +variable "nixos_system" { + type = string + description = "The nixos system to deploy" +} + +variable "target_host" { + type = string + description = "DNS host to deploy to" +} + +variable "target_user" { + type = string + default = "root" + description = "User to deploy as" +} + +variable "target_port" { + type = number + description = "SSH port used to connect to the target_host" + default = 22 +} + +variable "ssh_private_key" { + type = string + description = "Content of private key used to connect to the target_host. If set to - no key is passed to openssh and ssh will use its own configuration" + default = "-" +} + +variable "ignore_systemd_errors" { + type = bool + description = "Ignore systemd errors happening during deploy" + default = false +} + +variable "install_bootloader" { + type = bool + description = "Install/re-install the bootloader" + default = false +} diff --git a/launch/.terraform/modules/pixelfed.deploy/terraform/update-docs.sh b/launch/.terraform/modules/pixelfed.deploy/terraform/update-docs.sh new file mode 100755 index 0000000..b67d98a --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/terraform/update-docs.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" +files=() +find "${SCRIPT_DIR}"/* -type d | while read -r i; do + module_name=$(basename "$i") + markdown_file="${SCRIPT_DIR}/${module_name}.md" + terraform-docs --config "${SCRIPT_DIR}/.terraform-docs.yml" markdown table --output-file "${markdown_file}" --output-mode inject "${module_name}" + files+=("${markdown_file}") +done +cd .. +nix fmt -- --no-cache diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/flake-module.nix b/launch/.terraform/modules/pixelfed.deploy/tests/flake-module.nix new file mode 100644 index 0000000..784175f --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/flake-module.nix @@ -0,0 +1,33 @@ +{ withSystem, inputs, ... }: + +{ + flake.checks.x86_64-linux = withSystem "x86_64-linux" ( + { + pkgs, + system, + inputs', + config, + ... + }: + let + testInputsUnstable = { + inherit pkgs; + inherit (inputs.disko.nixosModules) disko; + nixos-anywhere = config.packages.nixos-anywhere; + kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-unstable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz"; + }; + testInputsStable = testInputsUnstable // { + kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-stable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz"; + }; + in + { + from-nixos = import ./from-nixos.nix testInputsUnstable; + from-nixos-stable = import ./from-nixos.nix testInputsStable; + from-nixos-with-sudo = import ./from-nixos-with-sudo.nix testInputsUnstable; + from-nixos-with-sudo-stable = import ./from-nixos-with-sudo.nix testInputsStable; + from-nixos-with-generated-config = import ./from-nixos-generate-config.nix testInputsUnstable; + from-nixos-build-on-remote = import ./from-nixos-build-on-remote.nix testInputsUnstable; + from-nixos-separated-phases = import ./from-nixos-separated-phases.nix testInputsUnstable; + } + ); +} diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-build-on-remote.nix b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-build-on-remote.nix new file mode 100644 index 0000000..5261713 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-build-on-remote.nix @@ -0,0 +1,56 @@ +(import ./lib/test-base.nix) ( + { pkgs, ... }: + { + name = "from-nixos-build-on-remote"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + }; + }; + testScript = '' + def create_test_machine( + oldmachine=None, **kwargs + ): # taken from <nixpkgs/nixos/tests/installer.nix> + start_command = [ + "${pkgs.qemu_test}/bin/qemu-kvm", + "-cpu", + "max", + "-m", + "1024", + "-virtfs", + "local,path=/nix/store,security_model=none,mount_tag=nix-store", + "-drive", + f"file={oldmachine.state_dir}/installed.qcow2,id=drive1,if=none,index=1,werror=report", + "-device", + "virtio-blk-pci,drive=drive1", + ] + machine = create_machine(start_command=" ".join(start_command), **kwargs) + driver.machines.append(machine) + return machine + start_all() + + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --build-on-remote \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + root@installed >&2 + """) + try: + installed.shutdown() + except BrokenPipeError: + # qemu has already exited + pass + new_machine = create_test_machine(oldmachine=installed, name="after_install") + new_machine.start() + hostname = new_machine.succeed("hostname").strip() + assert "nixos-anywhere" == hostname, f"'nixos-anywhere' != '{hostname}'" + ''; + } +) diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-generate-config.nix b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-generate-config.nix new file mode 100644 index 0000000..5b4d65d --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-generate-config.nix @@ -0,0 +1,71 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-generate-config"; + nodes = { + installer = + { pkgs, ... }: + { + imports = [ + ./modules/installer.nix + ]; + environment.systemPackages = [ pkgs.jq ]; + }; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + }; + }; + testScript = '' + start_all() + installer.fail("test -f /tmp/hw/config.nix") + installer.succeed("echo super-secret > /tmp/disk-1.key") + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ + --phases kexec,disko \ + --generate-hardware-config nixos-generate-config /tmp/hw/config.nix \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + root@installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-1.key)'" + echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-2.key)'" + """) + + installer.succeed("cat /tmp/hw/config.nix >&2") + installer.succeed("nix-instantiate --parse /tmp/hw/config.nix") + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "disk-2.key: 'another-secret'" in output, f"output does not contain expected values: {output}" + + installer.fail("test -f /test/hw/config.json") + + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ + --phases kexec,disko \ + --generate-hardware-config nixos-facter /tmp/hw/config.json \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-1.key)'" + echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-2.key)'" + """) + + installer.succeed("cat /tmp/hw/config.json >&2") + installer.succeed("jq < /tmp/hw/config.json") + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "disk-2.key: 'another-secret'" in output, f"output does not contain expected values: {output}" + ''; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-separated-phases.nix b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-separated-phases.nix new file mode 100644 index 0000000..2b267b6 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-separated-phases.nix @@ -0,0 +1,52 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-separated-phases"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.nixos = { + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + extraGroups = [ "wheel" ]; + }; + security.sudo.enable = true; + security.sudo.wheelNeedsPassword = false; + }; + }; + testScript = '' + start_all() + + with subtest("Kexec Phase"): + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --phases kexec \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + nixos@installed >&2 + """) + + with subtest("Disko Phase"): + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --phases disko \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + installed >&2 + """) + + with subtest("Install Phase"): + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --phases install \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + root@installed >&2 + """) + ''; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-with-sudo.nix b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-with-sudo.nix new file mode 100644 index 0000000..839dec3 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos-with-sudo.nix @@ -0,0 +1,40 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-with-sudo"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.nixos = { + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + extraGroups = [ "wheel" ]; + }; + security.sudo.enable = true; + security.sudo.wheelNeedsPassword = false; + }; + }; + testScript = '' + start_all() + installer.succeed("echo super-secret > /tmp/disk-1.key") + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --phases kexec,disko \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + nixos@installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-1.key)'" + echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat /tmp/disk-2.key)'" + """) + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "disk-2.key: 'another-secret'" in output, f"output does not contain expected values: {output}" + ''; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos.nix b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos.nix new file mode 100644 index 0000000..0f3d134 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/from-nixos.nix @@ -0,0 +1,76 @@ +(import ./lib/test-base.nix) ( + { pkgs, ... }: + { + name = "from-nixos"; + nodes = { + installer = ./modules/installer.nix; + installed = { + services.openssh.enable = true; + virtualisation.memorySize = 1024; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + }; + }; + testScript = '' + def create_test_machine( + oldmachine=None, **kwargs + ): # taken from <nixpkgs/nixos/tests/installer.nix> + start_command = [ + "${pkgs.qemu_test}/bin/qemu-kvm", + "-cpu", + "max", + "-m", + "1024", + "-virtfs", + "local,path=/nix/store,security_model=none,mount_tag=nix-store", + "-drive", + f"file={oldmachine.state_dir}/installed.qcow2,id=drive1,if=none,index=1,werror=report", + "-device", + "virtio-blk-pci,drive=drive1", + ] + machine = create_machine(start_command=" ".join(start_command), **kwargs) + driver.machines.append(machine) + return machine + start_all() + installer.succeed("mkdir -p /tmp/extra-files/var/lib/secrets") + installer.succeed("echo value > /tmp/extra-files/var/lib/secrets/key") + installer.succeed("mkdir -p /tmp/extra-files/home/user/.ssh") + installer.succeed("echo secretkey > /tmp/extra-files/home/user/.ssh/id_ed25519") + installer.succeed("echo publickey > /tmp/extra-files/home/user/.ssh/id_ed25519.pub") + installer.succeed("chmod 600 /tmp/extra-files/home/user/.ssh/id_ed25519") + ssh_key_path = "/etc/ssh/ssh_host_ed25519_key.pub" + ssh_key_output = installer.wait_until_succeeds(f""" + ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + root@installed cat {ssh_key_path} + """) + installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --kexec /etc/nixos-anywhere/kexec-installer \ + --extra-files /tmp/extra-files \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + --chown /home/user 1000:100 \ + --copy-host-keys \ + root@installed >&2 + """) + try: + installed.shutdown() + except BrokenPipeError: + # qemu has already exited + pass + new_machine = create_test_machine(oldmachine=installed, name="after_install") + new_machine.start() + hostname = new_machine.succeed("hostname").strip() + assert "nixos-anywhere" == hostname, f"'nixos-anywhere' != '{hostname}'" + content = new_machine.succeed("cat /var/lib/secrets/key").strip() + assert "value" == content, f"secret does not have expected value: {content}" + ssh_key_content = new_machine.succeed(f"cat {ssh_key_path}").strip() + assert ssh_key_content in ssh_key_output, "SSH host identity changed" + priv_key_perms = new_machine.succeed("stat -c %a /home/user/.ssh/id_ed25519").strip() + assert priv_key_perms == "600", f"unexpected permissions for private key: {priv_key_perms}" + user_dir_ownership = new_machine.succeed("stat -c %u:%g /home/user").strip() + assert user_dir_ownership == "1000:100", f"unexpected user home dir permissions: {user_dir_ownership}" + ''; + } +) diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/lib/test-base.nix b/launch/.terraform/modules/pixelfed.deploy/tests/lib/test-base.nix new file mode 100644 index 0000000..169d1fd --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/lib/test-base.nix @@ -0,0 +1,20 @@ +test: +{ + pkgs ? import <nixpkgs> { }, + nixos-anywhere ? pkgs.callPackage ../../src { }, + disko ? "${builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz"}/module.nix", + kexec-installer ? builtins.fetchurl "https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-noninteractive-${pkgs.stdenv.hostPlatform.system}.tar.gz", + ... +}: +let + inherit (pkgs) lib; + nixos-lib = import (pkgs.path + "/nixos/lib") { }; +in +(nixos-lib.runTest { + hostPkgs = pkgs; + # speed-up evaluation + defaults.documentation.enable = lib.mkDefault false; + # to accept external dependencies such as disko + node.specialArgs.inputs = { inherit nixos-anywhere disko kexec-installer; }; + imports = [ test ]; +}).config.result diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/modules/installer.nix b/launch/.terraform/modules/pixelfed.deploy/tests/modules/installer.nix new file mode 100644 index 0000000..e5c22d4 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/modules/installer.nix @@ -0,0 +1,22 @@ +{ pkgs, inputs, ... }: +let + disko = inputs.disko; + kexec-installer = inputs.kexec-installer; + system-to-install = pkgs.nixos [ + ./system-to-install.nix + disko + ]; +in +{ + system.activationScripts.rsa-key = '' + ${pkgs.coreutils}/bin/install -D -m600 ${./ssh-keys/ssh} /root/.ssh/install_key + ''; + + environment.systemPackages = [ inputs.nixos-anywhere ]; + + environment.etc = { + "nixos-anywhere/disko".source = system-to-install.config.system.build.diskoScriptNoDeps; + "nixos-anywhere/system-to-install".source = system-to-install.config.system.build.toplevel; + "nixos-anywhere/kexec-installer".source = kexec-installer; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh b/launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh new file mode 100644 index 0000000..db6049c --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQAAAJDpULAq6VCw +KgAAAAtzc2gtZWQyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQ +AAAECpjfl5WMMIDvEyZJTeXzRNFzpDpj4fqdIXHZauKAlE5MziQ+DhXsMxhx64DxUhR0G/ +DfSAz2pqAREDy/VUYEEFAAAACWxhc3NAbW9ycwECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh.pub b/launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh.pub new file mode 100644 index 0000000..e77d393 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/modules/ssh-keys/ssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMziQ+DhXsMxhx64DxUhR0G/DfSAz2pqAREDy/VUYEEF diff --git a/launch/.terraform/modules/pixelfed.deploy/tests/modules/system-to-install.nix b/launch/.terraform/modules/pixelfed.deploy/tests/modules/system-to-install.nix new file mode 100644 index 0000000..1181c94 --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/tests/modules/system-to-install.nix @@ -0,0 +1,47 @@ +{ modulesPath, self, ... }: +{ + imports = [ + (modulesPath + "/testing/test-instrumentation.nix") + (modulesPath + "/profiles/qemu-guest.nix") + (modulesPath + "/profiles/minimal.nix") + ]; + networking.hostName = "nixos-anywhere"; + documentation.enable = false; + hardware.enableAllFirmware = false; + networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs + boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms + disko.devices = { + disk = { + vda = { + device = "/dev/vda"; + type = "disk"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1M"; + type = "EF02"; + }; + ESP = { + size = "100M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/launch/.terraform/modules/pixelfed.deploy/treefmt/flake-module.nix b/launch/.terraform/modules/pixelfed.deploy/treefmt/flake-module.nix new file mode 100644 index 0000000..3e92a8e --- /dev/null +++ b/launch/.terraform/modules/pixelfed.deploy/treefmt/flake-module.nix @@ -0,0 +1,23 @@ +{ inputs, ... }: +{ + imports = [ + inputs.treefmt-nix.flakeModule + ]; + perSystem = + { config, pkgs, ... }: + { + treefmt = { + projectRootFile = "flake.nix"; + programs.mdsh.enable = true; + programs.nixpkgs-fmt.enable = true; + programs.shellcheck.enable = true; + programs.shfmt.enable = true; + programs.deno.enable = !pkgs.deno.meta.broken; + settings.formatter.shellcheck.options = [ + "-s" + "bash" + ]; + }; + formatter = config.treefmt.build.wrapper; + }; +} diff --git a/launch/.terraform/plugin_path b/launch/.terraform/plugin_path new file mode 100644 index 0000000..ccfab3b --- /dev/null +++ b/launch/.terraform/plugin_path @@ -0,0 +1,3 @@ +[ + "/nix/store/n77n9qyfi7pa5lwgwbbk740ykjccp6mg-opentofu-1.8.5/libexec/terraform-providers" +] \ No newline at end of file diff --git a/launch/.terraform/providers/registry.opentofu.org/hashicorp/external/2.3.4/linux_amd64 b/launch/.terraform/providers/registry.opentofu.org/hashicorp/external/2.3.4/linux_amd64 new file mode 120000 index 0000000..e74641a --- /dev/null +++ b/launch/.terraform/providers/registry.opentofu.org/hashicorp/external/2.3.4/linux_amd64 @@ -0,0 +1 @@ +/nix/store/mnqkwjg5v6sx86an34b4cn075h0lapz3-opentofu-1.8.7/libexec/terraform-providers/registry.opentofu.org/hashicorp/external/2.3.4/linux_amd64 \ No newline at end of file diff --git a/launch/.terraform/providers/registry.opentofu.org/hashicorp/null/3.2.3/linux_amd64 b/launch/.terraform/providers/registry.opentofu.org/hashicorp/null/3.2.3/linux_amd64 new file mode 120000 index 0000000..18952a1 --- /dev/null +++ b/launch/.terraform/providers/registry.opentofu.org/hashicorp/null/3.2.3/linux_amd64 @@ -0,0 +1 @@ +/nix/store/mnqkwjg5v6sx86an34b4cn075h0lapz3-opentofu-1.8.7/libexec/terraform-providers/registry.opentofu.org/hashicorp/null/3.2.3/linux_amd64 \ No newline at end of file diff --git a/launch/tf-env.nix b/launch/tf-env.nix new file mode 100644 index 0000000..eb1e316 --- /dev/null +++ b/launch/tf-env.nix @@ -0,0 +1,36 @@ +{ + lib, + pkgs, + sources ? import ../npins, + ... +}: +pkgs.stdenv.mkDerivation { + name = "tf-repo"; + src = ../.; + buildInputs = [ + (import ./tf.nix { inherit lib pkgs; }) + ]; + buildPhase = '' + runHook preBuild + pushd launch/ + + # pass nixos-anywhere path to TF through variable + # when switching TF to nix take this directly from `inputs` + # https://codeberg.org/kiara/e2ed-hetzner/commit/84b2a349d3e48ea2a17340bceff762d834fd4046 + # echo "{\"nixos-anywhere\": \"${sources.nixos-anywhere}\"}" > .auto.tfvars.json + + # point to the relevant providers + # tofu init -input=false + + popd + runHook postBuild + ''; + # FIXME: can the above even work without a connection? + installPhase = '' + runHook preInstall + + cp -r . $out + + runHook postInstall + ''; +} diff --git a/panel/default.nix b/panel/default.nix index 767802b..06d1a48 100644 --- a/panel/default.nix +++ b/panel/default.nix @@ -20,11 +20,18 @@ in pkgs.npins manage ]; - env = import ./env.nix { inherit lib pkgs; } // { - NPINS_DIRECTORY = toString ../npins; - CREDENTIALS_DIRECTORY = toString ./.credentials; - DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3"; - }; + env = + let + inherit (builtins) toString; + in + import ./env.nix { inherit lib pkgs; } + // { + NPINS_DIRECTORY = toString ../npins; + CREDENTIALS_DIRECTORY = toString ./.credentials; + DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3"; + # locally: use a fixed relative reference, so we can use our newest files without copying to the store + REPO_DIR = toString ../.; + }; shellHook = '' ln -sf ${sources.htmx}/dist/htmx.js src/panel/static/htmx.min.js # in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd. diff --git a/panel/env.nix b/panel/env.nix index 8780558..99a286d 100644 --- a/panel/env.nix +++ b/panel/env.nix @@ -3,12 +3,7 @@ pkgs, ... }: -let - inherit (builtins) toString; -in { - # locally: use a fixed relative reference, so we can use our newest files without copying to the store - REPO_DIR = toString ../.; BIN_PATH = lib.makeBinPath [ pkgs.lix pkgs.bash diff --git a/panel/nix/configuration.nix b/panel/nix/configuration.nix index 2735950..8b7cd95 100644 --- a/panel/nix/configuration.nix +++ b/panel/nix/configuration.nix @@ -29,6 +29,7 @@ let ((pkgs.formats.pythonVars { }).generate "settings.py" cfg.settings) (builtins.toFile "extra-settings.py" cfg.extra-settings) ]; + REPO_DIR = import ../../launch/tf-env.nix { inherit lib pkgs; }; }; python-environment = pkgs.python3.withPackages ( diff --git a/panel/nix/package.nix b/panel/nix/package.nix index 2f94fa8..0acd979 100644 --- a/panel/nix/package.nix +++ b/panel/nix/package.nix @@ -1,5 +1,6 @@ { lib, + pkgs, sqlite, python3, sources ? import ../../npins, @@ -59,7 +60,7 @@ python3.pkgs.buildPythonPackage { cp -v ${src}/manage.py $out/bin/manage.py chmod +x $out/bin/manage.py wrapProgram $out/bin/manage.py \ - --set REPO_DIR "${../..}" \ + --set REPO_DIR "${import ../../launch/tf-env.nix { inherit lib pkgs; }}" \ --prefix PYTHONPATH : "$PYTHONPATH" cp ${sources.htmx}/dist/htmx.min.js* $out/${python3.sitePackages}/panel/static/ ''; diff --git a/panel/src/panel/settings.py b/panel/src/panel/settings.py index 01c8896..ccdb830 100644 --- a/panel/src/panel/settings.py +++ b/panel/src/panel/settings.py @@ -238,21 +238,6 @@ if user_settings_file is not None: sys.modules["user_settings"] = module from user_settings import * # noqa: F403 # pyright: ignore [reportMissingImports] -def handle_double_hash(orig_path: str) -> str: - """Convert store paths to account for potentially double hashes. - - c.f. https://github.com/NixOS/nix/issues/10627 - """ - # local paths can stay as-is - if not STORE_PATTERN.match(orig_path): - return orig_path - # for /nix/store paths, account for double hashing - orig_name = orig_path.split("/")[-1] - cmd = ["find", "/nix/store/", "-maxdepth", "1", "-name", f"*{orig_name}"] - process = subprocess.run(cmd, capture_output=True) - fixed_name = process.stdout.decode("utf-8").split("\n")[0] - return fixed_name - # non-Django application settings # TODO(@fricklerhandwerk): @@ -263,4 +248,4 @@ def handle_double_hash(orig_path: str) -> str: bin_path=env['BIN_PATH'] # path of the root flake to trigger nixops from, see #94. # to deploy this should be specified, for dev just use a relative path. -repo_dir = handle_double_hash(env["REPO_DIR"]) +repo_dir = env["REPO_DIR"]