Compare commits

..

7 commits

Author SHA1 Message Date
7e465acb63
Even longer waits
Some checks failed
/ check-pre-commit (pull_request) Successful in 59s
/ check-website (pull_request) Successful in 2m0s
/ check-peertube (pull_request) Successful in 25m39s
/ check-pixelfed (pull_request) Failing after 25m37s
2025-02-12 18:46:25 +01:00
dd782b4ff5
Run the Pixelfed test in CI 2025-02-12 18:46:25 +01:00
a4cba3f697
Full rework of the Pixelfed test 2025-02-12 18:46:25 +01:00
e4ad4e266c
Fix Pixelfed service 2025-02-12 18:46:25 +01:00
e43296dce0
Clean up Mastodon and Peertube tests 2025-02-12 18:46:25 +01:00
9d27f2d98e
Clean up VM options 2025-02-12 18:46:24 +01:00
b63f9873fa
Enable CI on all ci/** branches 2025-02-12 18:46:24 +01:00
1381 changed files with 26396 additions and 9623 deletions

10
.envrc
View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
# the shebang is ignored, but nice for editors
# shellcheck shell=bash
if type -P lorri &>/dev/null; then
eval "$(lorri direnv)"
else
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
use_nix
fi

View file

@ -1,24 +0,0 @@
name: deploy-infra
on:
workflow_dispatch: # allows manual triggering
push:
branches:
- main
jobs:
deploy:
runs-on: native
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up SSH key for age secrets and SSH
run: |
env
mkdir -p ~/.ssh
echo "${{ secrets.CD_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
- name: Deploy
run: nix-shell --run 'eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519 && SHELL=$(which bash) nixops4 apply -v default'

View file

@ -1,5 +1,4 @@
on:
workflow_dispatch: # allows manual triggering
pull_request:
types:
- opened
@ -8,10 +7,30 @@ on:
push:
branches:
- main
- ci/**
jobs:
check-data-model:
check-pre-commit:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix-shell --run 'nix-unit ./deployment/data-model-test.nix'
- run: nix build .#checks.x86_64-linux.pre-commit -L
check-website:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: cd website && nix-build -A tests
- run: cd website && nix-build -A build
check-peertube:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.peertube -L
check-pixelfed:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.pixelfed -L

View file

@ -1,61 +0,0 @@
#!/bin/sh
set -euC
cd "$(dirname "$0")" || exit 3
nix_eval () { nix eval --impure --raw --expr "with builtins; $1"; }
system=$(nix_eval "currentSystem")
checks=$(nix_eval "toJSON (attrNames (getFlake (toString ../..)).checks.$system)")
output=$(mktemp)
{
cat <<EOF
name: Nix flake checks
on:
pull_request:
types:
- opened
- synchronize
- reopened
push:
branches:
- main
jobs:
_checks:
needs: $checks
runs-on: native
steps:
- run: true
_complete:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix-shell --run '.forgejo/workflows/nix-flake-check.sh check'
EOF
for check in $(echo "$checks" | jq -r .[]); do
cat <<EOF
$check:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.$system.$check -vL
EOF
done
} >| "$output"
target=$(basename "$0" .sh).yaml
if [ $# -eq 1 ] && [ "$1" = "check" ]; then
if ! diff_output=$(diff --color=always "$target" "$output"); then
printf >&2 'Changes detected (\e[31m< current\e[0m | \e[32m> generated\e[0m):\n%s\n' "$diff_output"
exit 1
fi
else
mv "$output" "$target"
fi

View file

@ -1,294 +0,0 @@
name: Nix flake checks
on:
pull_request:
types:
- opened
- synchronize
- reopened
push:
branches:
- main
jobs:
_checks:
needs: ["deployment-basic","deployment-cli","deployment-model-nixops4","deployment-model-ssh","deployment-model-tf","deployment-panel","nixops-deployment-providers-default","nixops-deployment-providers-fedi200","nixops-deployment-providers-fedi201","nixops-deployment-providers-forgejo-ci","nixops-deployment-providers-test","nixops-deployment-providers-vm02116","nixops-deployment-providers-vm02187","nixosConfigurations-fedi200","nixosConfigurations-fedi201","nixosConfigurations-forgejo-ci","nixosConfigurations-test01","nixosConfigurations-test02","nixosConfigurations-test03","nixosConfigurations-test04","nixosConfigurations-test05","nixosConfigurations-test06","nixosConfigurations-test11","nixosConfigurations-test12","nixosConfigurations-test13","nixosConfigurations-test14","nixosConfigurations-vm02116","nixosConfigurations-vm02187","panel","pre-commit","proxmox-basic","test-mastodon-service","test-peertube-service","vmOptions-fedi200","vmOptions-fedi201","vmOptions-test01","vmOptions-test02","vmOptions-test03","vmOptions-test04","vmOptions-test05","vmOptions-test06","vmOptions-test11","vmOptions-test12","vmOptions-test13","vmOptions-test14"]
runs-on: native
steps:
- run: true
_complete:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix-shell --run '.forgejo/workflows/nix-flake-check.sh check'
deployment-basic:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-basic -vL
deployment-cli:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-cli -vL
deployment-model-nixops4:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-nixops4 -vL
deployment-model-ssh:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-ssh -vL
deployment-model-tf:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-tf -vL
deployment-panel:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-panel -vL
nixops-deployment-providers-default:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-default -vL
nixops-deployment-providers-fedi200:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-fedi200 -vL
nixops-deployment-providers-fedi201:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-fedi201 -vL
nixops-deployment-providers-forgejo-ci:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-forgejo-ci -vL
nixops-deployment-providers-test:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-test -vL
nixops-deployment-providers-vm02116:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-vm02116 -vL
nixops-deployment-providers-vm02187:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-vm02187 -vL
nixosConfigurations-fedi200:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-fedi200 -vL
nixosConfigurations-fedi201:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-fedi201 -vL
nixosConfigurations-forgejo-ci:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-forgejo-ci -vL
nixosConfigurations-test01:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test01 -vL
nixosConfigurations-test02:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test02 -vL
nixosConfigurations-test03:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test03 -vL
nixosConfigurations-test04:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test04 -vL
nixosConfigurations-test05:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test05 -vL
nixosConfigurations-test06:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test06 -vL
nixosConfigurations-test11:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test11 -vL
nixosConfigurations-test12:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test12 -vL
nixosConfigurations-test13:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test13 -vL
nixosConfigurations-test14:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test14 -vL
nixosConfigurations-vm02116:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-vm02116 -vL
nixosConfigurations-vm02187:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-vm02187 -vL
panel:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.panel -vL
pre-commit:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.pre-commit -vL
proxmox-basic:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.proxmox-basic -vL
test-mastodon-service:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.test-mastodon-service -vL
test-peertube-service:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.test-peertube-service -vL
vmOptions-fedi200:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-fedi200 -vL
vmOptions-fedi201:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-fedi201 -vL
vmOptions-test01:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test01 -vL
vmOptions-test02:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test02 -vL
vmOptions-test03:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test03 -vL
vmOptions-test04:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test04 -vL
vmOptions-test05:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test05 -vL
vmOptions-test06:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test06 -vL
vmOptions-test11:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test11 -vL
vmOptions-test12:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test12 -vL
vmOptions-test13:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test13 -vL
vmOptions-test14:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test14 -vL

View file

@ -1,24 +0,0 @@
name: update-dependencies
on:
workflow_dispatch: # allows manual triggering
# FIXME: re-enable when manual run works
# schedule:
# - cron: '0 0 1 * *' # monthly
jobs:
lockfile:
runs-on: native
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update pins
run: nix-shell --run "npins --verbose update"
- name: Create PR
uses: https://github.com/KiaraGrouwstra/gitea-create-pull-request@f9f80aa5134bc5c03c38f5aaa95053492885b397
with:
remote-instance-api-version: v1
token: "${{ secrets.DEPLOY_KEY }}"
branch: npins-update
commit-message: "npins: update sources"
title: "npins: update sources"

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ tmp/
.proxmox
/.pre-commit-config.yaml
nixos.qcow2
.envrc
.direnv
result*
.nixos-test-history

144
README.md
View file

@ -4,154 +4,28 @@ This repository contains all the code and code-related files having to do with
[the Fediversity project](https://fediversity.eu/), with the notable exception
of [NixOps4 that is hosted on GitHub](https://github.com/nixops4/nixops4).
## Goals
Decentralise the operational responsibility for social media.
Enable a more robust market of hosting providers, by making it easy to migrate operations and data to different providers.
Note that Fediversity is not about self-hosting.
There already exist solutions for self-hosting, but they're not suitable for what we're trying to do.
The ones we're aware of require substantial technical knowledge and time commitment by operators, especially for scaling to thousands of users.
Not everyone has the expertise and time to run their own server.
## Interactions
To reach these goals, we aim to implement the following interactions between [actors](#actors) (depicted with rounded corners) and system components (see the [glossary](#glossary), depicted with rectangles).
![](https://git.fediversity.eu/Fediversity/meta/raw/branch/main/architecture-docs/interactions.svg)
## Actors
- Fediversity project team
The group working on this repository.
We are creating the deployment workflows and service configurations.
The project partners for Fediversity are:
- [NLnet Foundation](https://nlnet.nl/)
- Open Internet Discourse Foundation
- [NORDUnet](https://nordu.net/)
- [Tweag](https://www.tweag.io/)
Refer to [fediversity.eu](https://fediversity.eu) for more details about the project.
- Hosting provider
They provide and maintain the physical infrastructure, and run the software in this repository, through which operators interact with their deployments.
Hosting providers are technical administrators for these deployments, ensuring availability and appropriate performance.
We target small- to medium-scale hosting providers with 20+ physical machines.
- Operator
They select the applications they want to run (Mastodon, Pixelfed, Matrix, etc.).
They don't need to own hardware or deal with operations.
Operators administer their services in a non-technical fashion, e.g. as moderators.
They pay the hosting provider for registering a domain name, maintaining physical resources, and monitoring deployments.
Initially, Fediversity is targeted at organisations, such as universities.
- User
They are individuals that are not necessarily affiliated with any organisation.
They register an account on services (e.g. Mastodon) run by the operators, and e.g. post content.
Users dont need to administrate anything.
Given initial operators will be universities, users would be staff or students.
## Glossary
- [Fediverse](https://en.wikipedia.org/wiki/Fediverse)
A collection of social networking applications that can communicate with each other using a common protocol.
- Application
User-facing software (e.g. from Fediverse) run by the hosting provider for an operator.
- Configuration
A collection of settings for a machine running NixOS.
> Example: Configurations are deployed to VMs.
- Provision
Make a resource, such as a virtual machine, available for use.
> Example: We use [Proxmox](https://www.proxmox.com) to provision VMs for applications run by operators.
- Deploy
Put software, such as applications, onto computers.
The software includes technical configuration that links software components.
Most user-facing configuration remains untouched by the deployment process.
> Example: NixOps4 is used to deploy [Pixelfed](https://pixelfed.org).
- Migrate
Move service configurations and deployment state, including user data, from one hosting provider to another.
- [NixOps4](https://github.com/nixops4/nixops4)
A tool for deploying and managing resources through the Nix language.
NixOps4 development is supported by the Fediversity project
- Resource
A [resource for NixOps4](https://nixops.dev/manual/development/concept/resource.html) is any external entity that can be declared with NixOps4 expressions and manipulated with NixOps4, such as a virtual machine, an active NixOS configuration, a DNS entry, or customer database.
- Resource provider
A resource provider for NixOps4 is an executable that communicates between a resource and NixOps4 using a standardised protocol, allowing [CRUD operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) on the resources to be performed by NixOps4.
Refer to the [NixOps4 manual](https://nixops.dev/manual/development/resource-provider/index.html) for details.
> Example: We need a resource provider for obtaining deployment secrets from a database.
- Runtime backend
A type of digital environment one can run operating systems such as NixOS on, e.g. bare-metal, a hypervisor, or a container runtime.
- Runtime environment
The thing a deployment runs on, an interface against which the deployment is working. See runtime backend.
- Runtime config
Configuration logic specific to a runtime backend, e.g. how to deploy, how to access object storage.
## Development
All the code made for this project is freely licenced under [EUPL](https://en.m.wikipedia.org/wiki/European_Union_Public_Licence).
This means, anyone can use the work here to learn from it or change it according to their needs.
You can even read up on [development proceedings](https://git.fediversity.eu/Fediversity/meta).
Contact the project team if you have questions or suggestions, or if you're interested in using Fediversity software for your operations:
- E-mail: <mailto:contact@fediversity.eu>
- Mastodon: <https://mastodon.fediversity.eu/@fediversity>
### Content of this repository
## Content of this repository
Most of the directories in this repository have their own README going into more
details as to what they are for. As an overview:
- [`deployment/`](./deployment) contains work to generate a full Fediversity
deployment from a minimal configuration.
- [`deployment/`](./deployment) contains bits and pieces having to do with
auto-deployment of test VMs on a private Proxmox.
- [`infra/`](./infra) contains the configurations for the various VMs that are
in production for the project, for instance the Git instances or the Wiki, as
well as means to provision and set up new ones.
in production for the project, for instance the Git instances or the Wiki.
- [`keys/`](./keys) contains the public keys of the contributors to this project
as well as the systems that we administrate.
- [`machines/`](./machines) contains the code of our machines for internal infra and test VMs.
- [`panel/`](./panel) contains the code of our front-end.
- [`matrix/`](./matrix) contains everything having to do with setting up a
fully-featured Matrix server.
- [`secrets/`](./secrets) contains the secrets that need to get injected into
machine configurations.
- [`services/`](./services) contains our effort to make Fediverse applications
work seemlessly together in our specific setting.
- [`website/`](./website) contains the framework and the content of [the
Fediversity website](https://fediversity.eu/)

View file

@ -1,88 +0,0 @@
{
system ? builtins.currentSystem,
sources ? import ./npins,
pkgs ? import sources.nixpkgs { inherit system; },
}:
let
inherit (sources)
nixpkgs
git-hooks
gitignore
;
inherit (pkgs) lib;
inherit (import sources.flake-inputs) import-flake;
inputs = (import-flake { src = ./.; }).inputs;
inherit (inputs) nixops4;
panel = import ./panel { inherit sources system; };
pre-commit-check =
(import "${git-hooks}/nix" {
inherit nixpkgs system;
gitignore-nix-src = {
lib = import gitignore { inherit lib; };
};
}).run
{
src = ./.;
hooks =
let
## Add a directory here if pre-commit hooks shouldn't apply to it.
optout = [
"npins"
];
excludes = map (dir: "^${dir}/") optout;
addExcludes = lib.mapAttrs (_: c: c // { inherit excludes; });
in
addExcludes {
nixfmt-rfc-style.enable = true;
deadnix.enable = true;
trim-trailing-whitespace.enable = true;
shellcheck.enable = true;
};
};
in
{
# shell for testing TF directly
shell = pkgs.mkShellNoCC {
inherit (pre-commit-check) shellHook;
buildInputs = pre-commit-check.enabledPackages;
packages =
let
test-loop = pkgs.writeShellApplication {
name = "test-loop";
runtimeInputs = [
pkgs.watchexec
pkgs.nix-unit
];
text = ''
watchexec -w ${builtins.toString ./.} -- nix-unit ${builtins.toString ./deployment/data-model-test.nix} "$@"
'';
};
in
[
pkgs.npins
pkgs.nil
(pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { })
pkgs.openssh
pkgs.httpie
pkgs.jq
pkgs.diffutils
pkgs.nix-unit
test-loop
nixops4.packages.${system}.default
];
};
tests = {
inherit pre-commit-check;
panel = panel.tests;
};
# re-export inputs so they can be overridden granularly
# (they can't be accessed from the outside any other way)
inherit
inputs
sources
system
pkgs
;
}

View file

@ -1,123 +0,0 @@
# Deployment
This directory contains work to generate a full Fediversity deployment from a minimal configuration.
This is different from [`../services/`](../services) that focuses on one machine, providing a polished and unified interface to different Fediverse services.
## Data model
The core piece of the project is the [Fediversity data model](./data-model.nix), which describes all entities and their interactions.
What can be done with it is exemplified in the [evaluation tests](./data-model-test.nix).
Run `test-loop` in the development environment when hacking on the data model or adding tests.
## Checks
There are three levels of deployment checks: `basic`, `cli`, `panel`.
They can be found in subdirectories of [`check/`](./check).
They can be run as part of `nix flake check` or individually as:
``` console
$ nix build .#checks.<system>.deployment-<name> -vL
```
Since `nixops4 apply` operates on a flake, the tests take this repository's flake as a template.
This also why there are some dummy files that will be overwritten inside the test.
### Basic deployment check
The basic deployment check is here as a building block and sanity check.
It does not actually use any of the code in this directory but checks that our test strategy is sound and that basic NixOps4 functionalities are here.
It is a NixOS test featuring one deployer machine and two target machines.
The deployment simply adds `pkgs.hello` to one and `pkgs.cowsay` to the other.
It is heavily inspired by [a similar test in `nixops4-nixos`].
[a similar test in nixops4-nixos]: https://github.com/nixops4/nixops4-nixos/blob/main/test/default/nixosTest.nix
This test involves three nodes:
- `deployer` is the node that will perform the deployment using `nixops4 apply`.
Because the test runs in a sandboxed environment, `deployer` will not have access to internet, and therefore it must already have all store paths needed for the target nodes.
- “target machines” are two eponymous nodes on which the packages `hello` and `cowsay` will be deployed.
They start with a minimal configuration.
``` mermaid
flowchart LR
deployer["deployer<br><font size='1'>has store paths<br>runs nixops4</font>"]
subgraph target_machines["target machines"]
direction TB
hello
cowsay
end
deployer -->|deploys| target_machines
```
### Service deployment check using `nixops4 apply`
This check omits the panel by running a direct invocation of NixOps4.
It deploys some services and checks that they are indeed on the target machines, then cleans them up and checks whether that works, too.
It builds upon the basic deployment check.
This test involves seven nodes:
- `deployer` is the node that will perform the deployment using `nixops4 apply`.
Because the test runs in a sandboxed environment, `deployer` will not have access to internet, and therefore it must already have all store paths needed for the target nodes.
- “target machines” are four nodes — `garage`, `mastodon`, `peertube`, and `pixelfed` — on which the services will be deployed.
They start with a minimal configuration.
- `acme` is a node that runs [Pebble], a miniature ACME server to deliver the certificates that the services expect.
- [WIP] `client` is a node that runs a browser controlled by some Selenium scripts in order to check that the services are indeed running and are accessible.
[Pebble]: https://github.com/letsencrypt/pebble
``` mermaid
flowchart LR
classDef invisible fill:none,stroke:none
subgraph left [" "]
direction TB
deployer["deployer<br><font size='1'>has store paths<br>runs nixops4</font>"]
client["client<br><font size='1'>Selenium scripts</font>"]
end
subgraph middle [" "]
subgraph target_machines["target machines"]
direction TB
garage
mastodon
peertube
pixelfed
end
end
subgraph right [" "]
direction TB
acme["acme<br><font size='1'>runs Pebble</font>"]
end
left ~~~ middle ~~~ right
class left,middle,right invisible
deployer -->|deploys| target_machines
client -->|tests| mastodon
client -->|tests| peertube
client -->|tests| pixelfed
target_machines -->|get certs| acme
```
### Service deployment check from the FediPanel
This is a full deployment check running the [FediPanel](../panel) on the deployer machine, deploying some services through it and checking that they are indeed on the target machines, then cleans them up and checks whether that works, too.
It builds upon the basic and CLI deployment checks, the only difference being that `deployer` runs NixOps4 only indirectly via the panel, and the `client` node is the one that triggers the deployment via a browser, the way a human would.

113
deployment/README.org Normal file
View file

@ -0,0 +1,113 @@
#+title: Provisioning VMs via Proxmox
* Quick links
- Proxmox API doc :: https://pve.proxmox.com/pve-docs/api-viewer
- Fediversity Proxmox :: http://192.168.51.81:8006/
* Basic terminology
- Node :: physical host
* Fediversity Proxmox
- It is only accessible via Procolix's VPN:
- Get credentials for the VPN portal and Proxmox from [[https://git.fediversity.eu/kevin][Kevin]].
- Log in to the [[https://vpn.fediversity.eu/vpn-user-portal/home][VPN portal]].
- Create a *New Configuration*:
- Select *WireGuard (UDP)*
- Enter some name, e.g. ~fediversity~
- Click Download
- Write the WireGuard configuration to a file ~fediversity-vpn.config~ next to your NixOS configuration
- Add that file's path to ~.git/info/exclude~ and make sure it doesn't otherwise leak (for example, use [[https://github.com/ryantm/agenix][Agenix]] to manage secrets)
- To your NixOS configuration, add
#+begin_src nix
networking.wg-quick.interfaces.fediversity.configFile = toString ./fediversity-vpn.config;
#+end_src
- Select “Promox VE authentication server”.
- Ignore the “You do not have a valid subscription” message.
* Automatically
This directory contains scripts that can automatically provision or remove a
Proxmox VM. For now, they are tied to one node in the Fediversity Proxmox, but
it would not be difficult to make them more generic. Try:
#+begin_src sh
sh proxmox/provision.sh --help
sh proxmox/remove.sh --help
#+end_src
* Preparing the machine configuration
- It is nicer if the machine is a QEMU guest. On NixOS:
#+begin_src nix
services.qemuGuest.enable = true
#+end_src
- Choose name for your machine.
- Choose static IPs for your machine. The IPv4 and IPv6 subnets available for
Fediversity testing are:
- ~95.215.187.0/24~. Gateway is ~95.215.187.1~.
- ~2a00:51c0:13:1305::/64~. Gateway is ~2a00:51c0:13:1305::1~.
- I have been using id ~XXX~ (starting from ~001~), name ~fediXXX~, ~95.215.187.XXX~ and
~2a00:51c0:13:1305::XXX~.
- Name servers should be ~95.215.185.6~ and ~95.215.185.7~.
- Check [[https://netbox.protagio.org][Netbox]] to see which addresses are free.
* Manually via the GUI
** Upload your ISO
- Go to Fediversity proxmox.
- In the left view, expand under the node that you want and click on “local”.
- Select “ISO Images”, then click “Upload”.
- Note: You can also download from URL.
- Note: You should click on “local” and not “local-zfs”.
** Creating the VM
- Click “Create VM” at the top right corner.
*** General
- Node :: which node will host the VM; has to be the same
- VM ID :: Has to be unique, probably best to use the "xxxx" in "vm0xxxx" (yet to be decided)
- Name :: Usually "vm" + 5 digits, e.g. "vm02199"
- Resource pool :: Fediversity
*** OS
- Use CD/DVD disc image file (iso) ::
- Storage :: local, means storage of the node.
- ISO image :: select the image previously uploaded
No need to touch anything else
*** System
- BIOS :: OVMF (UEFI)
- EFI Storage :: ~linstor_storage~; this is a storage shared by all of the Proxmox machines.
- Pre-Enroll keys :: MUST be unchecked
- Qemu Agent :: check
*** Disks
- Tick “advanced” at the bottom.
- Disk size (GiB) :: 40 (depending on requirements)
- SSD emulation :: check (only visible if “Advanced” is checked)
- Discard :: check, so that blocks of removed data are cleared
*** CPU
- Sockets :: 1 (depending on requirements)
- Cores :: 2 (depending on requirements)
- Enable NUMA :: check
*** Memory
- Memory (MiB) :: choose what you want
- Ballooning Device :: leave checked (only visible if “Advanced” is checked)
*** Network
- Bridge :: ~vnet1306~. This is the provisioning bridge; we will change it later.
- Firewall :: uncheck, we will handle the firewall on the VM itself
*** Confirm
** Install and start the VM
- Start the VM a first time.
- Select the VM in the left panel. You might have to expand the node on which it is hosted.
- Select “Console” and start the VM.
- Install the VM as you would any other machine.
- [[Shutdown the VM]].
- After the VM has been installed:
- Select the VM again, then go to “Hardware”.
- Double click on the CD/DVD Drive line. Select “Do not use any media” and press OK.
- Double click on Network Device, and change the bridge to ~vnet1305~, the public bridge.
- Start the VM again.
** Remove the VM
- [[Shutdown the VM]].
- On the top right corner, click “More”, then “Remove”.
- Enter the ID of the machine.
- Check “Purge from job configurations”
- Check “Destroy unreferenced disks owned by guest”
- Click “Remove”.
** Move the VM to another node
- Make sure there is no ISO plugged in.
- Click on the VM. Click migrate. Choose target node. Go.
- Since the storage is shared, it should go pretty fast (~1 minute).
** Shutdown the VM
- Find the VM in the left panel.
- At the top right corner appears a “Shutdown” button with a submenu.
- Clicking “Shutdown” sends a signal to shutdown the machine. This might not work if the machine is not listening for that signal.
- Brutal solution: in the submenu, select “Stop”.
- The checkbox “Overrule active shutdown tasks” means that the machine should be stopped even if a shutdown is currently ongoing. This is particularly important if you have tried to shut the machine down normally just before.

View file

@ -1,9 +0,0 @@
{
targetMachines = [
"hello"
"cowsay"
];
pathToRoot = ../../..;
pathFromRoot = ./.;
useFlake = true;
}

View file

@ -1,19 +0,0 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
useFlake
;
}

View file

@ -1,36 +0,0 @@
{
inputs,
sources,
lib,
providers,
...
}:
let
inherit (import ./constants.nix) targetMachines pathToRoot pathFromRoot;
in
{
providers = {
inherit (inputs.nixops4.modules.nixops4Provider) local;
};
resources = lib.genAttrs targetMachines (nodeName: {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
../common/targetResource.nix
];
_module.args = { inherit inputs sources; };
inherit nodeName pathToRoot pathFromRoot;
nixos.module =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.${nodeName} ];
};
});
}

View file

@ -1,22 +0,0 @@
{
inputs = {
nixops4.follows = "nixops4-nixos/nixops4";
nixops4-nixos.url = "github:nixops4/nixops4-nixos";
};
outputs =
inputs:
import ./mkFlake.nix inputs (
{ inputs, sources, ... }:
{
imports = [
inputs.nixops4.modules.flake.default
];
nixops4Deployments.check-deployment-basic = {
imports = [ ./deployment/check/basic/deployment.nix ];
_module.args = { inherit inputs sources; };
};
}
);
}

View file

@ -1,54 +0,0 @@
{
inputs,
lib,
config,
...
}:
{
_class = "nixosTest";
name = "deployment-basic";
sourceFileset = lib.fileset.unions [
./constants.nix
./deployment.nix
(config.pathToCwd + "/flake-under-test.nix")
];
nodes.deployer =
{ pkgs, ... }:
{
environment.systemPackages = [
inputs.nixops4.packages.${pkgs.system}.default
];
# FIXME: sad times
system.extraDependencies = with pkgs; [
jq
jq.inputDerivation
];
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
cowsay
];
};
};
extraTestScript = ''
with subtest("Check the status before deployment"):
hello.fail("hello 1>&2")
cowsay.fail("cowsay 1>&2")
with subtest("Run the deployment"):
deployer.succeed("nixops4 apply check-deployment-basic --show-trace --no-interactive 1>&2")
with subtest("Check the deployment"):
hello.succeed("hello 1>&2")
cowsay.succeed("cowsay hi 1>&2")
'';
}

View file

@ -1,12 +0,0 @@
{
targetMachines = [
"garage"
"mastodon"
"peertube"
"pixelfed"
];
pathToRoot = ../../..;
pathFromRoot = ./.;
enableAcme = true;
useFlake = true;
}

View file

@ -1,20 +0,0 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
useFlake
;
}

View file

@ -1,59 +0,0 @@
{
inputs,
sources,
lib,
}:
let
inherit (builtins) fromJSON readFile listToAttrs;
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
;
makeTargetResource = nodeName: {
imports = [ ../common/targetResource.nix ];
_module.args = { inherit inputs sources; };
inherit
nodeName
pathToRoot
pathFromRoot
enableAcme
;
};
## The deployment function - what we are here to test!
##
## TODO: Modularise `deployment/default.nix` to get rid of the nested
## function calls.
makeTestDeployment =
args:
(import ../..)
{
inherit lib;
inherit (inputs) nixops4 nixops4-nixos;
fediversity = import ../../../services/fediversity;
}
(listToAttrs (
map (nodeName: {
name = "${nodeName}ConfigurationResource";
value = makeTargetResource nodeName;
}) targetMachines
))
(fromJSON (readFile ../../configuration.sample.json) // args);
in
{
check-deployment-cli-nothing = makeTestDeployment { };
check-deployment-cli-mastodon-pixelfed = makeTestDeployment {
mastodon.enable = true;
pixelfed.enable = true;
};
check-deployment-cli-peertube = makeTestDeployment {
peertube.enable = true;
};
}

View file

@ -1,26 +0,0 @@
{
inputs = {
nixops4.follows = "nixops4-nixos/nixops4";
nixops4-nixos.url = "github:nixops4/nixops4-nixos";
};
outputs =
inputs:
import ./mkFlake.nix inputs (
{
inputs,
sources,
lib,
...
}:
{
imports = [
inputs.nixops4.modules.flake.default
];
nixops4Deployments = import ./deployment/check/cli/deployments.nix {
inherit inputs sources lib;
};
}
);
}

View file

@ -1,139 +0,0 @@
{
inputs,
hostPkgs,
config,
lib,
...
}:
let
## Some places need a dummy file that will in fact never be used. We create
## it here.
dummyFile = hostPkgs.writeText "dummy" "";
in
{
_class = "nixosTest";
name = "deployment-cli";
sourceFileset = lib.fileset.unions [
./constants.nix
./deployments.nix
(config.pathToCwd + "/flake-under-test.nix")
# REVIEW: I would like to be able to grab all of `/deployment` minus
# `/deployment/check`, but I can't because there is a bunch of other files
# in `/deployment`. Maybe we can think of a reorg making things more robust
# here? (comment also in panel test)
../../default.nix
../../options.nix
../../configuration.sample.json
../../../services/fediversity
];
nodes.deployer =
{ pkgs, ... }:
{
environment.systemPackages = [
inputs.nixops4.packages.${pkgs.system}.default
];
## FIXME: The following dependencies are necessary but I do not
## understand why they are not covered by the fake node.
system.extraDependencies = with pkgs; [
peertube
peertube.inputDerivation
gixy
gixy.inputDerivation
];
system.extraDependenciesFromModule = {
imports = [ ../../../services/fediversity ];
fediversity = {
domain = "fediversity.net"; # would write `dummy` but that would not type
garage.enable = true;
mastodon = {
enable = true;
s3AccessKeyFile = dummyFile;
s3SecretKeyFile = dummyFile;
};
peertube = {
enable = true;
secretsFile = dummyFile;
s3AccessKeyFile = dummyFile;
s3SecretKeyFile = dummyFile;
};
pixelfed = {
enable = true;
s3AccessKeyFile = dummyFile;
s3SecretKeyFile = dummyFile;
};
temp.cores = 1;
temp.initialUser = {
username = "dummy";
displayName = "dummy";
email = "dummy";
passwordFile = dummyFile;
};
};
};
};
## NOTE: The target machines may need more RAM than the default to handle
## being deployed to, otherwise we get something like:
##
## pixelfed # [ 616.785499 ] sshd-session[1167]: Conection closed by 2001:db8:1::2 port 45004
## deployer # error: writing to file: No space left on device
## pixelfed # [ 616.788538 ] sshd-session[1151]: pam_unix(sshd:session): session closed for user port
## pixelfed # [ 616.793929 ] systemd-logind[719]: Session 4 logged out. Waiting for processes to exit.
## deployer # Error: Could not create resource
##
## These values have been trimmed down to the gigabyte.
nodes.mastodon.virtualisation.memorySize = 4 * 1024;
nodes.pixelfed.virtualisation.memorySize = 4 * 1024;
nodes.peertube.virtualisation.memorySize = 5 * 1024;
## FIXME: The test of presence of the services are very simple: we only
## check that there is a systemd service of the expected name on the
## machine. This proves at least that NixOps4 did something, and we cannot
## really do more for now because the services aren't actually working
## properly, in particular because of DNS issues. We should fix the services
## and check that they are working properly.
extraTestScript = ''
with subtest("Check the status of the services - there should be none"):
garage.fail("systemctl status garage.service")
mastodon.fail("systemctl status mastodon-web.service")
peertube.fail("systemctl status peertube.service")
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
with subtest("Run deployment with no services enabled"):
deployer.succeed("nixops4 apply check-deployment-cli-nothing --show-trace --no-interactive 1>&2")
with subtest("Check the status of the services - there should still be none"):
garage.fail("systemctl status garage.service")
mastodon.fail("systemctl status mastodon-web.service")
peertube.fail("systemctl status peertube.service")
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
with subtest("Run deployment with Mastodon and Pixelfed enabled"):
deployer.succeed("nixops4 apply check-deployment-cli-mastodon-pixelfed --show-trace --no-interactive 1>&2")
with subtest("Check the status of the services - expecting Garage, Mastodon and Pixelfed"):
garage.succeed("systemctl status garage.service")
mastodon.succeed("systemctl status mastodon-web.service")
peertube.fail("systemctl status peertube.service")
pixelfed.succeed("systemctl status phpfpm-pixelfed.service")
with subtest("Run deployment with only Peertube enabled"):
deployer.succeed("nixops4 apply check-deployment-cli-peertube --show-trace --no-interactive 1>&2")
with subtest("Check the status of the services - expecting Garage and Peertube"):
garage.succeed("systemctl status garage.service")
mastodon.fail("systemctl status mastodon-web.service")
peertube.succeed("systemctl status peertube.service")
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
'';
}

View file

@ -1,25 +0,0 @@
{
lib,
...
}:
let
inherit (lib) mkOption types;
in
{
options = {
host = mkOption {
type = types.str;
description = "name of the host to deploy to";
};
targetSystem = mkOption {
type = types.str;
description = "name of the host to deploy to";
};
sshOpts = mkOption {
description = "Extra SSH options (`-o`) to use.";
type = types.listOf types.str;
default = [ ];
example = "ConnectTimeout=60";
};
};
}

View file

@ -1,256 +0,0 @@
{
config,
system,
inputs ? (import ../../../default.nix { }).inputs, # XXX can't be serialized
sources ? import ../../../npins,
...
}@args:
let
# having this module's location (`self`) and (serializable) `args`, we know
# enough to make it re-call itself to extract different info elsewhere later.
# we use this to make a deployment script using the desired nixos config,
# which would otherwise not be serializable, while nix also makes it hard to
# produce its derivation to pass thru without a `nix-instantiate` call,
# which in turn would need to be passed the (unserializable) nixos config.
self = "deployment/check/common/data-model.nix";
inherit (sources) nixpkgs;
pkgs = import nixpkgs { inherit system; };
inherit (pkgs) lib;
deployment-config = config;
inherit (deployment-config)
nodeName
pathToRoot
targetSystem
sshOpts
;
inherit (lib) mkOption types;
eval =
module:
(lib.evalModules {
specialArgs = {
inherit pkgs inputs;
};
modules = [
module
../../data-model.nix
];
}).config;
fediversity = eval (
{ config, ... }:
{
config = {
resources.login-shell = {
description = "The operator needs to be able to log into the shell";
request =
{ ... }:
{
_class = "fediversity-resource-request";
options = {
wheel = mkOption {
description = "Whether the login user needs root permissions";
type = types.bool;
default = false;
};
packages = mkOption {
description = "Packages that need to be available in the user environment";
type = with types; attrsOf package;
};
};
};
policy =
{ config, ... }:
{
_class = "fediversity-resource-policy";
options = {
username = mkOption {
description = "Username for the operator";
type = types.str; # TODO: use the proper constraints from NixOS
};
wheel = mkOption {
description = "Whether to allow login with root permissions";
type = types.bool;
default = false;
};
};
config = {
resource-type = types.raw; # TODO: splice out the user type from NixOS
apply =
requests:
let
# Filter out requests that need wheel if policy doesn't allow it
validRequests = lib.filterAttrs (
_name: req: !req.login-shell.wheel || config.wheel
) requests.resources;
in
lib.optionalAttrs (validRequests != { }) {
${config.username} = {
isNormalUser = true;
packages =
with lib;
attrValues (concatMapAttrs (_name: request: request.login-shell.packages) validRequests);
extraGroups = lib.optional config.wheel "wheel";
};
};
};
};
};
applications.hello =
{ ... }:
{
description = ''Command-line tool that will print "Hello, world!" on the terminal'';
module =
{ ... }:
{
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation = cfg: {
resources = lib.optionalAttrs cfg.enable {
hello.login-shell.packages.hello = pkgs.hello;
};
};
};
environments =
let
mkNixosConfiguration =
environment: requests:
{ ... }:
{
imports = [
./data-model-options.nix
../common/sharedOptions.nix
../common/targetNode.nix
"${nixpkgs}/nixos/modules/profiles/qemu-guest.nix"
];
users.users = environment.config.resources."operator-environment".login-shell.apply {
resources = lib.filterAttrs (_name: value: value ? login-shell) (
lib.concatMapAttrs (
k': req: lib.mapAttrs' (k: lib.nameValuePair "${k'}.${k}") req.resources
) requests
);
};
};
in
{
single-nixos-vm-ssh = environment: {
resources."operator-environment".login-shell.username = "operator";
implementation =
{
required-resources,
deployment-name,
}:
{
ssh-host = {
nixos-configuration = mkNixosConfiguration environment required-resources;
system = targetSystem;
ssh = {
username = "root";
host = nodeName;
key-file = null;
inherit sshOpts;
};
module = self;
inherit args deployment-name;
root-path = pathToRoot;
};
};
};
single-nixos-vm-nixops4 = environment: {
resources."operator-environment".login-shell.username = "operator";
implementation =
{
required-resources,
...
}:
{
nixops4 =
{ providers, ... }:
{
providers = {
inherit (inputs.nixops4.modules.nixops4Provider) local;
};
resources.${nodeName} = {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
../common/targetResource.nix
];
nixos.module = mkNixosConfiguration environment required-resources;
_module.args = { inherit inputs sources; };
inherit (deployment-config) nodeName pathToRoot pathFromRoot;
};
};
};
};
single-nixos-vm-tf = environment: {
resources."operator-environment".login-shell.username = "operator";
implementation =
{
required-resources,
deployment-name,
}:
{
tf-host = {
nixos-configuration = mkNixosConfiguration environment required-resources;
system = targetSystem;
ssh = {
username = "root";
host = nodeName;
key-file = null;
inherit sshOpts;
};
module = self;
inherit args deployment-name;
root-path = pathToRoot;
};
};
};
};
};
options = {
"example-configuration" = mkOption {
type = config.configuration;
default = {
enable = true;
applications.hello.enable = true;
};
};
"ssh-deployment" =
let
env = config.environments."single-nixos-vm-ssh";
in
mkOption {
type = env.resource-mapping.output-type;
default = env.deployment {
deployment-name = "ssh-deployment";
configuration = config."example-configuration";
};
};
"nixops4-deployment" =
let
env = config.environments."single-nixos-vm-nixops4";
in
mkOption {
type = env.resource-mapping.output-type;
default = env.deployment {
deployment-name = "nixops4-deployment";
configuration = config."example-configuration";
};
};
"tf-deployment" =
let
env = config.environments."single-nixos-vm-tf";
in
mkOption {
type = env.resource-mapping.output-type;
default = env.deployment {
deployment-name = "tf-deployment";
configuration = config."example-configuration";
};
};
};
}
);
in
fediversity

View file

@ -1,107 +0,0 @@
{
inputs,
lib,
pkgs,
config,
sources,
...
}:
let
inherit (lib)
mkOption
mkForce
concatLists
types
;
in
{
_class = "nixos";
imports = [ ./sharedOptions.nix ];
options.system.extraDependenciesFromModule = mkOption {
type = types.deferredModule;
description = ''
Grab the derivations needed to build the given module and dump them in
system.extraDependencies. You want to put in this module a superset of
all the things that you will need on your target machines.
NOTE: This will work as long as the union of all these configurations do
not have conflicts that would prevent evaluation.
'';
default = { };
};
config = {
virtualisation = {
## NOTE: The deployer machines needs more RAM and default than the
## default. These values have been trimmed down to the gigabyte.
## Memory use is expected to be dominated by the NixOS evaluation,
## which happens on the deployer.
memorySize = 4 * 1024;
diskSize = 4 * 1024;
cores = 2;
};
nix.settings = {
substituters = mkForce [ ];
hashed-mirrors = null;
connect-timeout = 1;
extra-experimental-features = "flakes";
};
system.extraDependencies =
[
inputs.nixops4
inputs.nixops4-nixos
inputs.nixpkgs
sources.flake-parts
sources.nixpkgs
sources.flake-inputs
sources.git-hooks
pkgs.stdenv
pkgs.stdenvNoCC
]
++ (
let
## We build a whole NixOS system that contains the module
## `system.extraDependenciesFromModule`, only to grab its
## configuration and the store paths needed to build it and
## dump them in `system.extraDependencies`.
machine =
(pkgs.nixos [
./targetNode.nix
config.system.extraDependenciesFromModule
{
nixpkgs.hostPlatform = "x86_64-linux";
_module.args = { inherit inputs sources; };
enableAcme = config.enableAcme;
acmeNodeIP = config.acmeNodeIP;
}
]).config;
in
[
machine.system.build.toplevel.inputDerivation
machine.system.build.etc.inputDerivation
machine.system.build.etcBasedir.inputDerivation
machine.system.build.etcMetadataImage.inputDerivation
machine.system.build.extraUtils.inputDerivation
machine.system.path.inputDerivation
machine.system.build.setEnvironment.inputDerivation
machine.system.build.vm.inputDerivation
machine.system.build.bootStage1.inputDerivation
machine.system.build.bootStage2.inputDerivation
]
++ concatLists (
lib.mapAttrsToList (
_k: v: if v ? source.inputDerivation then [ v.source.inputDerivation ] else [ ]
) machine.environment.etc
)
);
};
}

View file

@ -1,206 +0,0 @@
{
inputs,
lib,
config,
hostPkgs,
sources,
...
}:
let
inherit (builtins)
concatStringsSep
toJSON
;
inherit (lib)
types
fileset
mkOption
genAttrs
attrNames
optionalString
;
inherit (hostPkgs)
runCommandNoCC
writeText
system
;
forConcat = xs: f: concatStringsSep "\n" (map f xs);
## We will need to override some inputs by the empty flake, so we make one.
emptyFlake = runCommandNoCC "empty-flake" { } ''
mkdir $out
echo "{ outputs = { self }: {}; }" > $out/flake.nix
'';
in
{
_class = "nixosTest";
imports = [
./sharedOptions.nix
];
options = {
## FIXME: I wish I could just use `testScript` but with something like
## `mkOrder` to put this module's string before something else.
extraTestScript = mkOption { };
sourceFileset = mkOption {
## FIXME: grab `lib.types.fileset` from NixOS, once upstreaming PR
## https://github.com/NixOS/nixpkgs/pull/428293 lands.
type = types.mkOptionType {
name = "fileset";
description = "fileset";
descriptionClass = "noun";
check = (x: (builtins.tryEval (fileset.unions [ x ])).success);
merge = (_: defs: fileset.unions (map (x: x.value) defs));
};
description = ''
A fileset that will be copied to the deployer node in the current
working directory. This should contain all the files that are
necessary to run that particular test, such as the NixOS
modules necessary to evaluate a deployment.
'';
};
};
config = {
sourceFileset = fileset.unions [
# NOTE: not the flake itself; it will be overridden.
../../../mkFlake.nix
../../../flake.lock
../../../npins
./sharedOptions.nix
./targetNode.nix
./targetResource.nix
];
acmeNodeIP = config.nodes.acme.networking.primaryIPAddress;
nodes =
{
deployer = {
imports = [ ./deployerNode.nix ];
_module.args = { inherit inputs sources; };
enableAcme = config.enableAcme;
acmeNodeIP = config.nodes.acme.networking.primaryIPAddress;
};
}
//
(
if config.enableAcme then
{
acme = {
## FIXME: This makes `nodes.acme` into a local resolver. Maybe this will
## break things once we play with DNS?
imports = [ "${inputs.nixpkgs}/nixos/tests/common/acme/server" ];
## We aren't testing ACME - we just want certificates.
systemd.services.pebble.environment.PEBBLE_VA_ALWAYS_VALID = "1";
};
}
else
{ }
)
//
genAttrs config.targetMachines (_: {
imports = [ ./targetNode.nix ];
_module.args = { inherit inputs sources; };
enableAcme = config.enableAcme;
acmeNodeIP = if config.enableAcme then config.nodes.acme.networking.primaryIPAddress else null;
});
testScript = ''
${forConcat (attrNames config.nodes) (n: ''
${n}.start()
'')}
${forConcat (attrNames config.nodes) (n: ''
${n}.wait_for_unit("multi-user.target")
'')}
## A subset of the repository that is necessary for this test. It will be
## copied inside the test. The smaller this set, the faster our CI, because we
## won't need to re-run when things change outside of it.
with subtest("Unpacking"):
deployer.succeed("cp -r --no-preserve=mode ${
fileset.toSource {
root = ../../..;
fileset = config.sourceFileset;
}
}/* .")
with subtest("Configure the network"):
${forConcat config.targetMachines (
tm:
let
targetNetworkJSON = writeText "target-network.json" (
toJSON config.nodes.${tm}.system.build.networkConfig
);
in
''
deployer.copy_from_host("${targetNetworkJSON}", "${config.pathFromRoot}/${tm}-network.json")
''
)}
with subtest("Configure the deployer key"):
deployer.succeed("""mkdir -p ~/.ssh && ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa""")
deployer_key = deployer.succeed("cat ~/.ssh/id_rsa.pub").strip()
${forConcat config.targetMachines (tm: ''
${tm}.succeed(f"mkdir -p /root/.ssh && echo '{deployer_key}' >> /root/.ssh/authorized_keys")
'')}
with subtest("Configure the target host key"):
${forConcat config.targetMachines (tm: ''
host_key = ${tm}.succeed("ssh-keyscan ${tm} | grep -v '^#' | cut -f 2- -d ' ' | head -n 1")
deployer.succeed(f"echo '{host_key}' > ${config.pathFromRoot}/${tm}_host_key.pub")
'')}
${
if config.useFlake then
''
## NOTE: This is super slow. It could probably be optimised in Nix, for
## instance by allowing to grab things directly from the host's store.
##
## NOTE: We use the repository as-is (cf `src` above), overriding only
## `flake.nix` by our `flake-under-test.nix`. We also override the flake
## lock file to use locally available inputs, as we cannot download them.
##
with subtest("Override the flake and its lock"):
deployer.succeed("cp ${config.pathFromRoot}/flake-under-test.nix flake.nix")
deployer.succeed("""
nix flake lock --extra-experimental-features 'flakes nix-command' \
--offline -v \
--override-input nixops4 ${inputs.nixops4.packages.${system}.flake-in-a-bottle} \
\
--override-input nixops4-nixos ${inputs.nixops4-nixos} \
--override-input nixops4-nixos/flake-parts ${inputs.nixops4-nixos.inputs.flake-parts} \
--override-input nixops4-nixos/flake-parts/nixpkgs-lib ${inputs.nixops4-nixos.inputs.flake-parts.inputs.nixpkgs-lib} \
--override-input nixops4-nixos/nixops4-nixos ${emptyFlake} \
--override-input nixops4-nixos/nixpkgs ${inputs.nixops4-nixos.inputs.nixpkgs} \
--override-input nixops4-nixos/nixops4 ${
inputs.nixops4-nixos.inputs.nixops4.packages.${system}.flake-in-a-bottle
} \
--override-input nixops4-nixos/git-hooks-nix ${emptyFlake} \
;
""")
''
else
""
}
${optionalString config.enableAcme ''
with subtest("Set up handmade DNS"):
deployer.succeed("echo '${config.nodes.acme.networking.primaryIPAddress}' > ${config.pathFromRoot}/acme_server_ip")
''}
${config.extraTestScript}
'';
};
}

View file

@ -1,70 +0,0 @@
/**
This file contains options shared by various components of the integration test, i.e. deployment resources, test nodes, target configurations, etc.
All these components are declared as modules, but are part of different evaluations, which is the options in this file can't be shared "directly".
Instead, each component imports this module and the same values are set for each of them from a common call site.
Not all components will use all the options, which allows not setting all the values.
*/
{ config, lib, ... }:
let
inherit (lib) mkOption types;
in
# `config` not set and imported from multiple places: no fixed module class
{
options = {
targetMachines = mkOption {
type = with types; listOf str;
description = ''
Names of the nodes in the NixOS test that are target machines. This is
used by the infrastructure to extract their network configuration, among
other things, and re-import it in the deployment.
'';
};
pathToRoot = mkOption {
type = types.path;
description = ''
Path from the location of the working directory to the root of the
repository.
'';
};
pathFromRoot = mkOption {
type = types.either types.path types.str;
description = ''
Path from the root of the repository to the working directory.
'';
apply = x: if lib.isString x then x else lib.path.removePrefix config.pathToRoot x;
};
pathToCwd = mkOption {
type = types.path;
description = ''
Path to the current working directory. This is a shortcut for
pathToRoot/pathFromRoot.
'';
default = config.pathToRoot + "/${config.pathFromRoot}";
};
enableAcme = mkOption {
type = types.bool;
description = ''
Whether to enable ACME in the NixOS test. This will add an ACME server
to the node and connect all the target machines to it.
'';
default = false;
};
acmeNodeIP = mkOption {
type = types.str;
description = ''
The IP of the ACME node in the NixOS test. This option will be set
during the test to the correct value.
'';
};
useFlake = lib.mkEnableOption "Use a flake in the test.";
};
}

View file

@ -1,69 +0,0 @@
{
inputs,
config,
lib,
modulesPath,
...
}:
let
testCerts = import "${inputs.nixpkgs}/nixos/tests/common/acme/server/snakeoil-certs.nix";
inherit (lib) mkIf mkMerge;
in
{
_class = "nixos";
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/../lib/testing/nixos-test-base.nix")
./sharedOptions.nix
];
config = mkMerge [
{
## Test framework disables switching by default. That might be OK by itself,
## but we also use this config for getting the dependencies in
## `deployer.system.extraDependencies`.
system.switch.enable = true;
nix = {
# short-cut network time-outs
settings.download-attempts = 1;
## Not used; save a large copy operation
channel.enable = false;
registry = lib.mkForce { };
};
services.openssh = {
enable = true;
settings.PermitRootLogin = "yes";
};
networking.firewall.allowedTCPPorts = [ 22 ];
## Test VMs don't have a bootloader by default.
boot.loader.grub.enable = false;
}
(mkIf config.enableAcme {
security.acme = {
acceptTerms = true;
defaults.email = "test@test.com";
defaults.server = "https://acme.test/dir";
};
security.pki.certificateFiles = [
## NOTE: This certificate is the one used by the Pebble HTTPS server.
## This is NOT the root CA of the Pebble server. We do add it here so
## that Pebble clients can talk to its API, but this will not allow
## those machines to verify generated certificates.
testCerts.ca.cert
];
## FIXME: it is a bit sad that all this logistics is necessary. look into
## better DNS stuff
networking.extraHosts = "${config.acmeNodeIP} acme.test";
})
];
}

View file

@ -1,51 +0,0 @@
{
inputs,
lib,
config,
sources,
...
}:
let
inherit (builtins) readFile;
inherit (lib) trim mkOption types;
in
{
_class = "nixops4Resource";
imports = [ ./sharedOptions.nix ];
options = {
nodeName = mkOption {
type = types.str;
description = ''
The name of the node in the NixOS test;
needed for recovering the node configuration to prepare its deployment.
'';
};
};
config = {
ssh = {
host = config.nodeName;
hostPublicKey = readFile (config.pathToCwd + "/${config.nodeName}_host_key.pub");
};
nixpkgs = inputs.nixpkgs;
nixos.module = {
imports = [
./targetNode.nix
(lib.modules.importJSON (config.pathToCwd + "/${config.nodeName}-network.json"))
];
_module.args = { inherit inputs sources; };
enableAcme = config.enableAcme;
acmeNodeIP = trim (readFile (config.pathToCwd + "/acme_server_ip"));
nixpkgs.hostPlatform = "x86_64-linux";
};
};
}

View file

@ -1,9 +0,0 @@
{
targetMachines = [
"nixops4"
];
pathToRoot = ../../..;
pathFromRoot = ./.;
enableAcme = true;
useFlake = true;
}

View file

@ -1,22 +0,0 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../../data-model.nix
../../function.nix
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
useFlake
;
}

View file

@ -1,29 +0,0 @@
{
inputs = {
nixops4.follows = "nixops4-nixos/nixops4";
nixops4-nixos.url = "github:nixops4/nixops4-nixos";
};
outputs =
inputs:
import ./mkFlake.nix inputs (
{ inputs, ... }:
let
system = "x86_64-linux";
in
{
imports = [
inputs.nixops4.modules.flake.default
];
nixops4Deployments.check-deployment-model =
(import ./deployment/check/common/data-model.nix {
inherit system inputs;
config = {
inherit (import ./deployment/check/data-model-nixops4/constants.nix) pathToRoot pathFromRoot;
nodeName = "nixops4";
};
})."nixops4-deployment".nixops4;
}
);
}

View file

@ -1,52 +0,0 @@
{
lib,
config,
inputs,
...
}:
{
_class = "nixosTest";
imports = [
../common/data-model-options.nix
];
name = "deployment-model";
sourceFileset = lib.fileset.unions [
../../data-model.nix
../../function.nix
../common/data-model.nix
../common/data-model-options.nix
./constants.nix
(config.pathToCwd + "/flake-under-test.nix")
];
nodes.deployer =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
inputs.nixops4.packages.${system}.default
jq
];
# FIXME: sad times
system.extraDependencies = with pkgs; [
jq
jq.inputDerivation
];
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
];
};
};
extraTestScript = ''
with subtest("nixops4"):
nixops4.fail("hello 1>&2")
deployer.succeed("nixops4 apply check-deployment-model --show-trace --verbose --no-interactive 1>&2")
nixops4.succeed("su - operator -c hello 1>&2")
'';
}

View file

@ -1,12 +0,0 @@
{
targetMachines = [
"ssh"
];
# stablize path, as just the path would yield distinct paths when applied multiple times
pathToRoot = builtins.path {
path = ../../..;
name = "root";
};
pathFromRoot = "/deployment/check/data-model-ssh";
enableAcme = true;
}

View file

@ -1,21 +0,0 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../../data-model.nix
../../function.nix
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
;
}

View file

@ -1,68 +0,0 @@
{
lib,
pkgs,
...
}:
let
inherit (import ./constants.nix) pathToRoot pathFromRoot;
inherit (pkgs) system;
deployment-config = {
inherit pathToRoot pathFromRoot;
nodeName = "ssh";
targetSystem = system;
sshOpts = [ ];
};
deploy =
(import ../common/data-model.nix {
inherit system;
config = deployment-config;
# opt not to pass `inputs`, as we could only pass serializable arguments through to its self-call
})."ssh-deployment".ssh-host.run;
in
{
_class = "nixosTest";
imports = [
../common/data-model-options.nix
];
name = "deployment-model";
sourceFileset = lib.fileset.unions [
../../data-model.nix
../../function.nix
../../nixos.nix
../../run/ssh-single-host/run.sh
../../../npins/default.nix
../../../npins/sources.json
../common/data-model.nix
../common/data-model-options.nix
./constants.nix
];
nodes.deployer =
{ ... }:
{
environment.systemPackages = [
deploy
];
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
];
};
};
extraTestScript = ''
with subtest("Check the status before deployment"):
ssh.fail("hello 1>&2")
with subtest("Run the deployment"):
deployer.succeed("""
${lib.getExe deploy}
""")
ssh.wait_for_unit("multi-user.target")
ssh.succeed("su - operator -c hello 1>&2")
'';
}

View file

@ -1,11 +0,0 @@
{
targetMachines = [
"target"
];
pathToRoot = builtins.path {
path = ../../..;
name = "root";
};
pathFromRoot = "/deployment/check/data-model-tf";
enableAcme = true;
}

View file

@ -1,21 +0,0 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../../data-model.nix
../../function.nix
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
;
}

View file

@ -1,61 +0,0 @@
{
lib,
pkgs,
...
}:
let
inherit (import ./constants.nix) pathToRoot pathFromRoot;
inherit (pkgs) system;
deployment-config = {
inherit pathToRoot pathFromRoot;
nodeName = "target";
targetSystem = system;
sshOpts = [ ];
};
deploy =
(import ../common/data-model.nix {
inherit system;
config = deployment-config;
# opt not to pass `inputs`, as we could only pass serializable arguments through to its self-call
})."tf-deployment".tf-host.run;
in
{
_class = "nixosTest";
imports = [
../common/data-model-options.nix
];
name = "deployment-model";
sourceFileset = lib.fileset.unions [
../../run/tf-single-host/run.sh
];
nodes.deployer =
{ ... }:
{
environment.systemPackages = [
deploy
];
# needed only when building from deployer
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
];
};
};
extraTestScript = ''
with subtest("Check the status before deployment"):
target.fail("hello 1>&2")
with subtest("Run the deployment"):
deployer.succeed("""
${lib.getExe deploy}
""")
target.wait_for_unit("multi-user.target")
target.succeed("su - operator -c hello 1>&2")
'';
}

View file

@ -1,12 +0,0 @@
{
targetMachines = [
"garage"
"mastodon"
"peertube"
"pixelfed"
];
pathToRoot = ../../..;
pathFromRoot = ./.;
enableAcme = true;
useFlake = true;
}

View file

@ -1,20 +0,0 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
useFlake
;
}

View file

@ -1,58 +0,0 @@
{
inputs,
sources,
lib,
}:
let
inherit (builtins) fromJSON listToAttrs;
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
;
makeTargetResource = nodeName: {
imports = [ ../common/targetResource.nix ];
_module.args = { inherit inputs sources; };
inherit
nodeName
pathToRoot
pathFromRoot
enableAcme
;
};
## The deployment function - what we are here to test!
##
## TODO: Modularise `deployment/default.nix` to get rid of the nested
## function calls.
makeTestDeployment =
args:
(import ../..)
{
inherit lib;
inherit (inputs) nixops4 nixops4-nixos;
fediversity = import ../../../services/fediversity;
}
(listToAttrs (
map (nodeName: {
name = "${nodeName}ConfigurationResource";
value = makeTargetResource nodeName;
}) targetMachines
))
args;
in
makeTestDeployment (
fromJSON (
let
env = builtins.getEnv "DEPLOYMENT";
in
if env == "" then
throw "The DEPLOYMENT environment needs to be set. You do not want to use this deployment unless in the `deployment-panel` NixOS test."
else
env
)
)

View file

@ -1,26 +0,0 @@
{
inputs = {
nixops4.follows = "nixops4-nixos/nixops4";
nixops4-nixos.url = "github:nixops4/nixops4-nixos";
};
outputs =
inputs:
import ./mkFlake.nix inputs (
{
inputs,
sources,
lib,
...
}:
{
imports = [
inputs.nixops4.modules.flake.default
];
nixops4Deployments.check-deployment-panel = import ./deployment/check/panel/deployment.nix {
inherit inputs sources lib;
};
}
);
}

View file

@ -1,378 +0,0 @@
{
inputs,
lib,
hostPkgs,
config,
...
}:
let
inherit (lib)
getExe
;
## Some places need a dummy file that will in fact never be used. We create
## it here.
dummyFile = hostPkgs.writeText "dummy" "dummy";
panelPort = 8000;
panelUser = "test";
panelEmail = "test@test.com";
panelPassword = "ouiprdaaa43"; # panel's manager complains if too close to username or email
fediUser = "test";
fediEmail = "test@test.com";
fediPassword = "testtest";
fediName = "Testy McTestface";
toPythonBool = b: if b then "True" else "False";
interactWithPanel =
{
baseUri,
enableMastodon,
enablePeertube,
enablePixelfed,
}:
hostPkgs.writers.writePython3Bin "interact-with-panel"
{
libraries = with hostPkgs.python3Packages; [ selenium ];
flakeIgnore = [
"E302" # expected 2 blank lines, found 0
"E303" # too many blank lines
"E305" # expected 2 blank lines after end of function or class
"E501" # line too long (> 79 characters)
"E731" # do not assign lambda expression, use a def
];
}
''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
print("Create and configure driver...")
options = Options()
options.add_argument("--headless")
options.binary_location = "${getExe hostPkgs.firefox-unwrapped}"
service = webdriver.FirefoxService(executable_path="${getExe hostPkgs.geckodriver}")
driver = webdriver.Firefox(options=options, service=service)
driver.set_window_size(1280, 960)
driver.implicitly_wait(360)
driver.command_executor.set_timeout(3600)
print("Open login page...")
driver.get("${baseUri}/login/")
print("Enter username...")
driver.find_element(By.XPATH, "//input[@name = 'username']").send_keys("${panelUser}")
print("Enter password...")
driver.find_element(By.XPATH, "//input[@name = 'password']").send_keys("${panelPassword}")
print("Click Login button...")
driver.find_element(By.XPATH, "//button[normalize-space() = 'Login']").click()
print("Open configuration page...")
driver.get("${baseUri}/configuration/")
# Helpers to actually set and not add or switch input values.
def input_set(elt, keys):
elt.clear()
elt.send_keys(keys)
def checkbox_set(elt, new_value):
if new_value != elt.is_selected():
elt.click()
print("Enable Fediversity...")
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'enable']"), True)
print("Fill in initialUser info...")
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.username']"), "${fediUser}")
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.password']"), "${fediPassword}")
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.email']"), "${fediEmail}")
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.displayName']"), "${fediName}")
print("Enable services...")
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'mastodon.enable']"), ${toPythonBool enableMastodon})
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'peertube.enable']"), ${toPythonBool enablePeertube})
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'pixelfed.enable']"), ${toPythonBool enablePixelfed})
print("Start deployment...")
driver.find_element(By.XPATH, "//button[@id = 'deploy-button']").click()
print("Wait for deployment status to show up...")
get_deployment_result = lambda d: d.find_element(By.XPATH, "//div[@id = 'deployment-result']//p")
WebDriverWait(driver, timeout=3660, poll_frequency=10).until(get_deployment_result)
deployment_result = get_deployment_result(driver).get_attribute('innerHTML')
print("Quit...")
driver.quit()
match deployment_result:
case 'Deployment Succeeded':
print("Deployment has succeeded; exiting normally")
exit(0)
case 'Deployment Failed':
print("Deployment has failed; exiting with return code `1`")
exit(1)
case _:
print(f"Unexpected deployment result: {deployment_result}; exiting with return code `2`")
exit(2)
'';
in
{
_class = "nixosTest";
name = "deployment-panel";
sourceFileset = lib.fileset.unions [
./constants.nix
./deployment.nix
(config.pathToCwd + "/flake-under-test.nix")
# REVIEW: I would like to be able to grab all of `/deployment` minus
# `/deployment/check`, but I can't because there is a bunch of other files
# in `/deployment`. Maybe we can think of a reorg making things more robust
# here? (comment also in CLI test)
../../default.nix
../../options.nix
../../../services/fediversity
];
## The panel's module sets `nixpkgs.overlays` which clashes with
## `pkgsReadOnly`. We disable it here.
node.pkgsReadOnly = false;
nodes.deployer =
{ pkgs, ... }:
{
imports = [
(import ../../../panel { }).module
];
## FIXME: This should be in the common stuff.
security.acme = {
acceptTerms = true;
defaults.email = "test@test.com";
defaults.server = "https://acme.test/dir";
};
security.pki.certificateFiles = [
(import "${inputs.nixpkgs}/nixos/tests/common/acme/server/snakeoil-certs.nix").ca.cert
];
networking.extraHosts = "${config.acmeNodeIP} acme.test";
services.panel = {
enable = true;
production = true;
domain = "deployer";
secrets = {
SECRET_KEY = dummyFile;
};
port = panelPort;
deployment = {
flake = "/run/fedipanel/flake";
name = "check-deployment-panel";
};
};
environment.systemPackages = [ pkgs.expect ];
## FIXME: The following dependencies are necessary but I do not
## understand why they are not covered by the fake node.
system.extraDependencies = with pkgs; [
peertube
peertube.inputDerivation
gixy # a configuration checker for nginx
gixy.inputDerivation
];
system.extraDependenciesFromModule = {
imports = [ ../../../services/fediversity ];
fediversity = {
domain = "fediversity.net"; # would write `dummy` but that would not type
garage.enable = true;
mastodon = {
enable = true;
s3AccessKeyFile = dummyFile;
s3SecretKeyFile = dummyFile;
};
peertube = {
enable = true;
secretsFile = dummyFile;
s3AccessKeyFile = dummyFile;
s3SecretKeyFile = dummyFile;
};
pixelfed = {
enable = true;
s3AccessKeyFile = dummyFile;
s3SecretKeyFile = dummyFile;
};
temp.cores = 1;
temp.initialUser = {
username = "dummy";
displayName = "dummy";
email = "dummy";
passwordFile = dummyFile;
};
};
};
};
nodes.client =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
httpie
dnsutils # for `dig`
openssl
cacert
wget
python3
python3Packages.selenium
firefox-unwrapped
geckodriver
];
security.pki.certificateFiles = [
config.nodes.acme.test-support.acme.caCert
];
networking.extraHosts = "${config.acmeNodeIP} acme.test";
};
## NOTE: The target machines may need more RAM than the default to handle
## being deployed to, otherwise we get something like:
##
## pixelfed # [ 616.785499 ] sshd-session[1167]: Conection closed by 2001:db8:1::2 port 45004
## deployer # error: writing to file: No space left on device
## pixelfed # [ 616.788538 ] sshd-session[1151]: pam_unix(sshd:session): session closed for user port
## pixelfed # [ 616.793929 ] systemd-logind[719]: Session 4 logged out. Waiting for processes to exit.
## deployer # Error: Could not create resource
##
## These values have been trimmed down to the gigabyte.
nodes.mastodon.virtualisation.memorySize = 4 * 1024;
nodes.pixelfed.virtualisation.memorySize = 4 * 1024;
nodes.peertube.virtualisation.memorySize = 5 * 1024;
## FIXME: The test of presence of the services are very simple: we only
## check that there is a systemd service of the expected name on the
## machine. This proves at least that NixOps4 did something, and we cannot
## really do more for now because the services aren't actually working
## properly, in particular because of DNS issues. We should fix the services
## and check that they are working properly.
extraTestScript = ''
## TODO: We want a nicer way to control where the FediPanel consumes its
## flake, which can default to the store but could also be somewhere else if
## someone wanted to change the code of the flake.
##
with subtest("Give the panel access to the flake"):
deployer.succeed("mkdir /run/fedipanel /run/fedipanel/flake >&2")
deployer.succeed("cp -R . /run/fedipanel/flake >&2")
deployer.succeed("chown -R panel:panel /run/fedipanel >&2")
## TODO: I want a programmatic way to provide an SSH key to the panel (and
## therefore NixOps4). This should happen either in the Python code, but
## maybe it is fair that that one picks up on the user's key? It could
## also be in the Nix packaging.
##
with subtest("Set up the panel's SSH keys"):
deployer.succeed("mkdir /home/panel/.ssh >&2")
deployer.succeed("cp -R /root/.ssh/* /home/panel/.ssh >&2")
deployer.succeed("chown -R panel:panel /home/panel/.ssh >&2")
deployer.succeed("chmod 600 /home/panel/.ssh/* >&2")
## TODO: This is a hack to accept the root CA used by Pebble on the client
## machine. Pebble randomizes everything, so the only way to get it is to
## call the /roots/0 endpoint at runtime, leaving not much margin for a nice
## Nixy way of adding the certificate. There is no way around it as this is
## by design in Pebble, showing in fact that Pebble was not the appropriate
## tool for our use and that nixpkgs does not in fact provide an easy way to
## generate _usable_ certificates in NixOS tests. I suggest we merge this,
## and track the task to set it up in a cleaner way. I would tackle this in
## a subsequent PR, and hopefully even contribute this BetterWay(tm) to
## nixpkgs. — Niols
##
with subtest("Set up ACME root CA on client"):
client.succeed("""
cd /etc/ssl/certs
curl -o pebble-root-ca.pem https://acme.test:15000/roots/0
curl -o pebble-intermediate-ca.pem https://acme.test:15000/intermediates/0
{ cat ca-bundle.crt
cat pebble-root-ca.pem
cat pebble-intermediate-ca.pem
} > new-ca-bundle.crt
rm ca-bundle.crt ca-certificates.crt
mv new-ca-bundle.crt ca-bundle.crt
ln -s ca-bundle.crt ca-certificates.crt
""")
## TODO: I would hope for a more declarative way to add users. This should
## be handled by the Nix packaging of the FediPanel. — Niols
##
with subtest("Create panel user"):
deployer.succeed("""
expect -c '
spawn manage createsuperuser --username ${panelUser} --email ${panelEmail}
expect "Password: "; send "${panelPassword}\\n";
expect "Password (again): "; send "${panelPassword}\\n"
interact
' >&2
""")
with subtest("Check the status of the services - there should be none"):
garage.fail("systemctl status garage.service")
mastodon.fail("systemctl status mastodon-web.service")
peertube.fail("systemctl status peertube.service")
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
with subtest("Run deployment with no services enabled"):
client.succeed("${
interactWithPanel {
baseUri = "https://deployer";
enableMastodon = false;
enablePeertube = false;
enablePixelfed = false;
}
}/bin/interact-with-panel >&2")
with subtest("Check the status of the services - there should still be none"):
garage.fail("systemctl status garage.service")
mastodon.fail("systemctl status mastodon-web.service")
peertube.fail("systemctl status peertube.service")
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
with subtest("Run deployment with Mastodon and Pixelfed enabled"):
client.succeed("${
interactWithPanel {
baseUri = "https://deployer";
enableMastodon = true;
enablePeertube = false;
enablePixelfed = true;
}
}/bin/interact-with-panel >&2")
with subtest("Check the status of the services - expecting Garage, Mastodon and Pixelfed"):
garage.succeed("systemctl status garage.service")
mastodon.succeed("systemctl status mastodon-web.service")
peertube.fail("systemctl status peertube.service")
pixelfed.succeed("systemctl status phpfpm-pixelfed.service")
with subtest("Run deployment with only Peertube enabled"):
client.succeed("${
interactWithPanel {
baseUri = "https://deployer";
enableMastodon = false;
enablePeertube = true;
enablePixelfed = false;
}
}/bin/interact-with-panel >&2")
with subtest("Check the status of the services - expecting Garage and Peertube"):
garage.succeed("systemctl status garage.service")
mastodon.fail("systemctl status mastodon-web.service")
peertube.succeed("systemctl status peertube.service")
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
'';
}

View file

@ -1,37 +0,0 @@
{
runNixOSTest,
sources,
system,
}:
let
pkgs = import sources.nixpkgs-stable {
inherit system;
overlays = [ overlay ];
};
overlay = _: _: {
inherit
(import "${sources.proxmox-nixos}/pkgs" {
craneLib = pkgs.callPackage "${sources.crane}/lib" { };
# breaks from https://github.com/NixOS/nixpkgs/commit/06b354eb2dc535c57e9b4caaa16d79168f117a26,
# which updates libvncserver to 0.9.15, which was not yet patched at https://git.proxmox.com/?p=vncterm.git.
inherit pkgs;
# not so picky about version for our purposes
pkgs-unstable = pkgs;
})
proxmox-ve
pve-ha-manager
;
};
in
runNixOSTest {
node.specialArgs = {
inherit
sources
pkgs
;
};
imports = [
./proxmoxTest.nix
];
}

View file

@ -1,87 +0,0 @@
# https://github.com/SaumonNet/proxmox-nixos/blob/main/tests/vm.nix
{
pkgs,
...
}:
let
# tracking non-tarball downloads seems unsupported still in npins:
# https://github.com/andir/npins/issues/163
minimalIso = pkgs.fetchurl {
url = "https://releases.nixos.org/nixos/24.05/nixos-24.05.7139.bcba2fbf6963/nixos-minimal-24.05.7139.bcba2fbf6963-x86_64-linux.iso";
hash = "sha256-plre/mIHdIgU4xWU+9xErP+L4i460ZbcKq8iy2n4HT8=";
};
in
{
name = "proxmox-basic";
nodes.mypve =
{ sources, ... }:
{
imports = [
"${sources.proxmox-nixos}/modules/proxmox-ve"
];
services.proxmox-ve = {
enable = true;
ipAddress = "192.168.1.1";
vms = {
myvm1 = {
vmid = 100;
memory = 1024;
cores = 1;
sockets = 1;
kvm = true;
scsi = [ { file = "local:16"; } ];
cdrom = "local:iso/minimal.iso";
};
};
};
virtualisation = {
additionalPaths = [ minimalIso ];
diskSize = 4096;
memorySize = 2048;
};
};
testScript = ''
machine.start()
machine.wait_for_unit("pveproxy.service")
assert "running" in machine.succeed("pveproxy status")
# Copy Iso
machine.succeed("mkdir -p /var/lib/vz/template/iso/")
machine.succeed("cp ${minimalIso} /var/lib/vz/template/iso/minimal.iso")
# Declarative VM creation
machine.wait_for_unit("multi-user.target")
machine.succeed("qm stop 100 --timeout 0")
# Seabios VM creation
machine.succeed(
"qm create 101 --kvm 0 --bios seabios -cdrom local:iso/minimal.iso",
"qm start 101",
"qm stop 101 --timeout 0"
)
# Legacy ovmf vm creation
machine.succeed(
"qm create 102 --kvm 0 --bios ovmf -cdrom local:iso/minimal.iso",
"qm start 102",
"qm stop 102 --timeout 0"
)
# UEFI ovmf vm creation
machine.succeed(
"qm create 103 --kvm 0 --bios ovmf --efidisk0 local:4,efitype=4m -cdrom local:iso/minimal.iso",
"qm start 103",
"qm stop 103 --timeout 0"
)
# UEFI ovmf vm creation with secure boot
machine.succeed(
"qm create 104 --kvm 0 --bios ovmf --efidisk0 local:4,efitype=4m,pre-enrolled-keys=1 -cdrom local:iso/minimal.iso",
"qm start 104",
"qm stop 104 --timeout 0"
)
'';
}

View file

@ -1,12 +0,0 @@
{
"domain": "fediversity.net",
"mastodon": { "enable": false },
"peertube": { "enable": false },
"pixelfed": { "enable": false },
"initialUser": {
"displayName": "Testy McTestface",
"username": "test",
"password": "testtest",
"email": "test@test.com"
}
}

View file

@ -1,211 +0,0 @@
let
inherit (import ../default.nix { }) pkgs inputs;
inherit (pkgs) lib;
inherit (lib) mkOption types;
eval =
module:
(lib.evalModules {
specialArgs = {
inherit pkgs inputs;
};
modules = [
module
./data-model.nix
];
}).config;
inherit (inputs.nixops4.lib) mkDeployment;
in
{
_class = "nix-unit";
test-eval = {
/**
This tests a very simple arrangement that features all ingredients of the Fediversity business logic:
application, resource, environment, deployment; and wires it all up in one end-to-end exercise.
- The dummy resource is a login shell made available for some user.
- The dummy application is `hello` that requires a shell to be deployed.
- The dummy environment is a single NixOS VM that hosts one login shell, for the operator.
- The dummy configuration enables the `hello` application.
This will produce a NixOps4 deployment for a NixOS VM with a login shell for the operator and `hello` available.
*/
expr =
let
fediversity = eval (
{ config, ... }:
{
config = {
resources.login-shell = {
description = "The operator needs to be able to log into the shell";
request =
{ ... }:
{
_class = "fediversity-resource-request";
options = {
wheel = mkOption {
description = "Whether the login user needs root permissions";
type = types.bool;
default = false;
};
packages = mkOption {
description = "Packages that need to be available in the user environment";
type = with types; attrsOf package;
};
};
};
policy =
{ config, ... }:
{
_class = "fediversity-resource-policy";
options = {
username = mkOption {
description = "Username for the operator";
type = types.str; # TODO: use the proper constraints from NixOS
};
wheel = mkOption {
description = "Whether to allow login with root permissions";
type = types.bool;
default = false;
};
};
config = {
resource-type = types.raw; # TODO: splice out the user type from NixOS
apply =
requests:
let
# Filter out requests that need wheel if policy doesn't allow it
validRequests = lib.filterAttrs (
_name: req: !req.login-shell.wheel || config.wheel
) requests.resources;
in
lib.optionalAttrs (validRequests != { }) {
${config.username} = {
isNormalUser = true;
packages =
with lib;
attrValues (concatMapAttrs (_name: request: request.login-shell.packages) validRequests);
extraGroups = lib.optional config.wheel "wheel";
};
};
};
};
};
applications.hello =
{ ... }:
{
description = ''Command-line tool that will print "Hello, world!" on the terminal'';
module =
{ ... }:
{
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation = cfg: {
resources = lib.optionalAttrs cfg.enable {
hello.login-shell.packages.hello = pkgs.hello;
};
};
};
environments.single-nixos-vm =
{ config, ... }:
{
resources."operator-environment".login-shell.username = "operator";
implementation = requests: {
nixops4 = (
{ providers, ... }:
{
providers = {
inherit (inputs.nixops4.modules.nixops4Provider) local;
};
resources.the-machine = {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
];
nixos.module =
{ ... }:
{
users.users = config.resources."operator-environment".login-shell.apply {
resources = lib.filterAttrs (_name: value: value ? login-shell) (
lib.concatMapAttrs (
k': req: lib.mapAttrs' (k: lib.nameValuePair "${k'}.${k}") req.resources
) requests
);
};
};
};
}
);
};
};
};
options = {
"example-configuration" = mkOption {
type = config.configuration;
readOnly = true;
default = {
enable = true;
applications.hello.enable = true;
};
};
"example-deployment" = mkOption {
type = config.environments.single-nixos-vm.resource-mapping.output-type;
readOnly = true;
default = config.environments.single-nixos-vm.deployment config."example-configuration";
};
};
}
);
resources =
fediversity.applications.hello.resources
fediversity."example-configuration".applications.hello;
hello-shell = resources.resources.hello.login-shell;
environment = fediversity.environments.single-nixos-vm.resources."operator-environment".login-shell;
result = mkDeployment {
modules = [
(fediversity.environments.single-nixos-vm.deployment fediversity."example-configuration")
];
};
in
{
number-of-resources = with lib; length (attrNames fediversity.resources);
inherit (fediversity) example-configuration;
hello-package-exists = hello-shell.packages ? hello;
wheel-required = hello-shell.wheel;
wheel-allowed = environment.wheel;
operator-shell =
let
operator = (environment.apply resources).operator;
in
{
inherit (operator) isNormalUser;
packages = map (p: "${p.pname}") operator.packages;
extraGroups = operator.extraGroups;
};
deployment = {
inherit (result) _type;
deploymentFunction = lib.isFunction result.deploymentFunction;
getProviders = lib.isFunction result.getProviders;
};
};
expected = {
number-of-resources = 1;
example-configuration = {
enable = true;
applications.hello.enable = true;
};
hello-package-exists = true;
wheel-required = false;
wheel-allowed = false;
operator-shell = {
isNormalUser = true;
packages = [ "hello" ];
extraGroups = [ ];
};
deployment = {
_type = "nixops4Deployment";
deploymentFunction = true;
getProviders = true;
};
};
};
}

View file

@ -1,469 +0,0 @@
{
lib,
config,
inputs,
pkgs,
sources ? import ../npins,
...
}:
let
inherit (lib) mkOption types;
inherit (lib.types)
attrTag
attrsOf
deferredModuleWith
functionTo
nullOr
optionType
raw
str
submodule
;
toBash =
v:
lib.replaceStrings [ "\"" ] [ "\\\"" ] (
if lib.isPath v || builtins.isNull v then
toString v
else if lib.isString v then
v
else
lib.strings.toJSON v
);
withPackages = packages: {
makeWrapperArgs = [
"--prefix"
"PATH"
":"
"${lib.makeBinPath packages}"
];
};
writeConfig =
{
system,
module,
root-path,
deployment-type,
deployment-name,
args,
}:
builtins.toString (
pkgs.writers.writeText "configuration.nix" ''
import ${root-path}/deployment/nixos.nix {
system = "${system}";
configuration = (import "${root-path}/${module}" (builtins.fromJSON "${
lib.replaceStrings [ "\"" ] [ "\\\"" ] (lib.strings.toJSON args)
}")).${deployment-name}.${deployment-type}.nixos-configuration;
}
''
);
functionType = submodule ./function.nix;
application-resources = submodule {
options.resources = mkOption {
# TODO: maybe transpose, and group the resources by type instead
type = attrsOf (
attrTag (
lib.mapAttrs (_name: resource: mkOption { type = submodule resource.request; }) config.resources
)
);
};
};
nixops4Deployment = types.deferredModuleWith {
staticModules = [
inputs.nixops4.modules.nixops4Deployment.default
{
_class = "nixops4Deployment";
_module.args = {
resourceProviderSystem = pkgs.system;
resources = { };
};
}
];
};
nixos-configuration = mkOption {
description = "A NixOS configuration.";
type = raw;
};
host-ssh = mkOption {
description = "SSH connection info to connect to a single host.";
type = submodule {
options = {
host = mkOption {
description = "the host to access by SSH";
type = str;
};
username = mkOption {
description = "the SSH user to use";
type = nullOr str;
default = null;
};
key-file = mkOption {
description = "path to the user's SSH private key";
type = nullOr str;
example = "/root/.ssh/id_ed25519";
};
sshOpts = mkOption {
description = "Extra SSH options (`-o`) to use.";
type = types.listOf str;
default = [ ];
example = "ConnectTimeout=60";
};
};
};
};
# FIXME allow custom deployment types
# FIXME make deployments environment resources?
deployment-type = attrTag {
ssh-host = mkOption {
description = "A deployment by SSH to update a single existing NixOS host.";
type = submodule (ssh-host: {
options = {
system = mkOption {
description = "The architecture of the system to deploy to.";
type = types.str;
};
inherit nixos-configuration;
ssh = host-ssh;
module = mkOption {
description = "The module to call to obtain the NixOS configuration from.";
type = types.str;
};
args = mkOption {
description = "The arguments with which to call the module to obtain the NixOS configuration.";
type = types.attrs;
};
deployment-name = mkOption {
description = "The name of the deployment for which to obtain the NixOS configuration.";
type = types.str;
};
root-path = mkOption {
description = "The path to the root of the repository.";
type = types.path;
};
run = mkOption {
type = types.package;
# error: The option `ssh-deployment.ssh-host.run' is read-only, but it's set multiple times.
# readOnly = true;
default =
let
inherit (ssh-host.config)
system
ssh
module
args
deployment-name
root-path
;
inherit (ssh)
host
username
key-file
sshOpts
;
environment = {
key_file = key-file;
ssh_opts = sshOpts;
inherit
host
username
;
nixos_conf = writeConfig {
inherit
system
module
args
deployment-name
root-path
;
deployment-type = "ssh-host";
};
};
in
pkgs.writers.writeBashBin "deploy-sh.sh"
(withPackages [
pkgs.jq
])
''
env ${
toString (lib.mapAttrsToList (k: v: "${k}=\"${toBash v}\"") environment)
} bash ./deployment/run/ssh-single-host/run.sh
'';
};
};
});
};
nixops4 = mkOption {
description = "A NixOps4 NixOS deployment. For an example, see https://github.com/nixops4/nixops4-nixos/blob/main/example/deployment.nix.";
type = nixops4Deployment;
};
tf-host = mkOption {
description = "A Terraform deployment by SSH to update a single existing NixOS host.";
type = submodule (tf-host: {
options = {
system = mkOption {
description = "The architecture of the system to deploy to.";
type = types.str;
};
inherit nixos-configuration;
ssh = host-ssh;
module = mkOption {
description = "The module to call to obtain the NixOS configuration from.";
type = types.str;
};
args = mkOption {
description = "The arguments with which to call the module to obtain the NixOS configuration.";
type = types.attrs;
};
deployment-name = mkOption {
description = "The name of the deployment for which to obtain the NixOS configuration.";
type = types.str;
};
root-path = mkOption {
description = "The path to the root of the repository.";
type = types.path;
};
run = mkOption {
type = types.package;
# error: The option `tf-deployment.tf-host.run' is read-only, but it's set multiple times.
# readOnly = true;
default =
let
inherit (tf-host.config)
system
ssh
module
args
deployment-name
root-path
;
inherit (ssh)
host
username
key-file
sshOpts
;
environment = {
key_file = key-file;
ssh_opts = sshOpts;
inherit
host
username
;
nixos_conf = writeConfig {
inherit
system
module
args
deployment-name
root-path
;
deployment-type = "tf-host";
};
};
tf-env = pkgs.callPackage ./run/tf-single-host/tf-env.nix { };
in
pkgs.writers.writeBashBin "deploy-tf.sh"
(withPackages [
pkgs.jq
(pkgs.callPackage ./run/tf-single-host/tf.nix { inherit sources; })
])
''
env ${toString (lib.mapAttrsToList (k: v: "TF_VAR_${k}=\"${toBash v}\"") environment)} \
tf_env=${tf-env} bash ./deployment/run/tf-single-host/run.sh
'';
};
};
});
};
};
in
{
options = {
resources = mkOption {
description = "Collection of deployment resources that can be required by applications and policed by hosting providers";
type = attrsOf (
submodule (
{ ... }:
{
_class = "fediversity-resource";
options = {
description = mkOption {
description = "Description of the resource to help application module authors and hosting providers to work with it";
type = types.str;
};
request = mkOption {
description = "Options for declaring resource requirements by an application, a description of how the resource is consumed or accessed";
type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-request"; } ]; };
};
policy = mkOption {
description = "Options for configuring the resource policy for the hosting provider, a description of how the resource is made available";
type = deferredModuleWith {
staticModules = [
(policy: {
_class = "fediversity-resource-policy";
options.resource-type = mkOption {
description = "The type of resource this policy configures";
type = types.optionType;
};
# TODO(@fricklerhandwerk): we may want to make the function type explicit here: `application-resources -> resource-type`
# and then also rename this to be consistent with the application's resource mapping
options.apply = mkOption {
description = "Apply the policy to a request";
type = functionTo policy.config.resource-type;
};
})
];
};
};
};
}
)
);
};
applications = mkOption {
description = "Collection of Fediversity applications";
type = attrsOf (
submodule (application: {
_class = "fediversity-application";
options = {
description = mkOption {
description = "Description to be shown in the application overview";
type = types.str;
};
module = mkOption {
description = "Operator-facing configuration options for the application";
type = deferredModuleWith { staticModules = [ { _class = "fediversity-application-config"; } ]; };
};
implementation = mkOption {
description = "Mapping of application configuration to deployment resources, a description of what an application needs to run";
type = application.config.config-mapping.function-type;
};
resources = mkOption {
description = "Compute resources required by an application";
type = application.config.config-mapping.function-type;
readOnly = true;
default = application.config.config-mapping.apply;
};
# TODO(@fricklerhandwerk): this needs a better name
config-mapping = mkOption {
description = "Function type for the mapping from application configuration to required resources";
type = functionType;
readOnly = true;
default = {
input-type = submodule application.config.module;
output-type = application-resources;
implementation = application.config.implementation;
};
};
};
})
);
};
environments = mkOption {
description = "Run-time environments for Fediversity applications to be deployed to";
type = attrsOf (
submodule (environment: {
_class = "fediversity-environment";
options = {
resources = mkOption {
description = ''
Resources made available by the hosting provider, and their policies.
Setting this is optional, but provides a place to declare that information for programmatic use in the resource mapping.
'';
# TODO: maybe transpose, and group the resources by type instead
type = attrsOf (
attrTag (
lib.mapAttrs (_name: resource: mkOption { type = submodule resource.policy; }) config.resources
)
);
};
implementation = mkOption {
description = "Mapping of resources required by applications to available resources; the result can be deployed";
type = environment.config.resource-mapping.function-type;
};
resource-mapping = mkOption {
description = "Function type for the mapping from resources to a deployment";
type = functionType;
readOnly = true;
default = {
input-type = submodule {
options = {
deployment-name = mkOption {
type = types.str;
};
required-resources = mkOption {
type = attrsOf application-resources;
};
};
};
output-type = deployment-type;
implementation = environment.config.implementation;
};
};
config-mapping = mkOption {
description = "Mapping from a configuration to a deployment";
type = functionType;
readOnly = true;
default = {
input-type = submodule {
options = {
deployment-name = mkOption {
type = types.str;
};
configuration = mkOption {
type = config.configuration;
};
};
};
output-type = deployment-type;
implementation =
{
deployment-name,
configuration,
}:
# TODO: check cfg.enable.true
let
required-resources = lib.mapAttrs (
name: application-settings: config.applications.${name}.resources application-settings
) configuration.applications;
in
environment.config.resource-mapping.apply { inherit required-resources deployment-name; };
};
};
# TODO(@fricklerhandwerk): maybe this should be a separate thing such as `fediversity-setup`,
# which makes explicit which applications and environments are available.
# then the deployments can simply be the result of the function application baked into this module.
deployment = mkOption {
description = "Generate a deployment from a configuration, by applying an environment's resource policies to the applications' resource mappings";
type = environment.config.config-mapping.function-type;
readOnly = true;
default = environment.config.config-mapping.apply;
};
};
})
);
};
configuration = mkOption {
description = "Configuration type declaring options to be set by operators";
type = optionType;
readOnly = true;
default = submodule {
options = {
enable = lib.mkEnableOption {
description = "your Fediversity configuration";
};
applications = lib.mapAttrs (
_name: application:
mkOption {
description = application.description;
type = submodule application.module;
default = { };
}
) config.applications;
};
};
};
};
}

View file

@ -1,218 +0,0 @@
## `makeMakeDeployment` -- Function to help hosting providers make a
## `makeDeployment` function.
##
## https://factoryfactoryfactory.net/
## Generic utilities used in this function, eg. nixpkgs, NixOps4 providers, etc.
## REVIEW: We should maybe be more specific than just `inputs`.
{
lib,
nixops4,
nixops4-nixos,
fediversity,
}:
## Information on the hosting provider's infrastructure. This is where we inform
## this function of where it can find eg. Proxmox.
{
## Four NixOS configuration resource modules for four services. Those are VMs
## that are already deployed and on which we will push our configurations.
##
## - Ultimately, we just want a pool of VMs, or even just a Proxmox.
## - Each machine is flagged for a certain use case until we control DNS.
garageConfigurationResource,
mastodonConfigurationResource,
peertubeConfigurationResource,
pixelfedConfigurationResource,
}:
## From the hosting provider's perspective, the function is meant to be
## partially applied only until here.
## Information on the specific deployment that we request. This is the
## information coming from the FediPanel.
##
## FIXME: lock step the interface with the definitions in the FediPanel
panelConfigNullable:
let
inherit (lib) mkIf;
## The convertor from module options to JSON schema does not generate proper
## JSON schema types, forcing us to use nullable fields for default values.
## However, working with those fields in the deployment code is annoying (and
## unusual for Nix programmers), so we sanitize the input here and add back
## the default value by hand.
nonNull = x: v: if x == null then v else x;
panelConfig = {
domain = nonNull panelConfigNullable.domain "fediversity.net";
initialUser = nonNull panelConfigNullable.initialUser {
displayName = "Testy McTestface";
username = "test";
password = "testtest";
email = "test@test.com";
};
mastodon = nonNull panelConfigNullable.mastodon { enable = false; };
peertube = nonNull panelConfigNullable.peertube { enable = false; };
pixelfed = nonNull panelConfigNullable.pixelfed { enable = false; };
};
in
## Regular arguments of a NixOps4 deployment module.
{ config, providers, ... }:
let
cfg = config.deployment;
in
{
_class = "nixops4Deployment";
options = {
deployment = lib.mkOption {
description = ''
Configuration to be deployed
'';
# XXX(@fricklerhandwerk):
# misusing this will produce obscure errors that will be truncated by NixOps4
type = lib.types.submodule ./options.nix;
default = panelConfig;
};
};
config = {
providers = { inherit (nixops4.modules.nixops4Provider) local; };
resources =
let
## NOTE: All of these secrets are publicly available in this source file
## and will end up in the Nix store. We don't care as they are only ever
## used for testing anyway.
##
## FIXME: Generate and store in NixOps4's state.
mastodonS3KeyConfig =
{ pkgs, ... }:
{
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
};
peertubeS3KeyConfig =
{ pkgs, ... }:
{
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
};
pixelfedS3KeyConfig =
{ pkgs, ... }:
{
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
};
makeConfigurationResource = resourceModule: config: {
type = providers.local.exec;
imports = [
nixops4-nixos.modules.nixops4Resource.nixos
resourceModule
{
## NOTE: With NixOps4, there are several levels and all of them live
## in the NixOS module system:
##
## 1. Each NixOps4 deployment is a module.
## 2. Each NixOps4 resource is a module. This very comment is
## inside an attrset imported as a module in a resource.
## 3. Each NixOps4 'configuration' resource contains an attribute
## 'nixos.module', itself a NixOS configuration module.
nixos.module =
{ ... }:
{
imports = [
config
fediversity
];
};
}
];
};
in
{
garage-configuration = makeConfigurationResource garageConfigurationResource (
{ pkgs, ... }:
mkIf (cfg.mastodon.enable || cfg.peertube.enable || cfg.pixelfed.enable) {
fediversity = {
inherit (cfg) domain;
garage.enable = true;
pixelfed = pixelfedS3KeyConfig { inherit pkgs; };
mastodon = mastodonS3KeyConfig { inherit pkgs; };
peertube = peertubeS3KeyConfig { inherit pkgs; };
};
}
);
mastodon-configuration = makeConfigurationResource mastodonConfigurationResource (
{ pkgs, ... }:
mkIf cfg.mastodon.enable {
fediversity = {
inherit (cfg) domain;
temp.initialUser = {
inherit (cfg.initialUser) username email displayName;
# FIXME: disgusting, but nvm, this is going to be replaced by
# proper central authentication at some point
passwordFile = pkgs.writeText "password" cfg.initialUser.password;
};
mastodon = mastodonS3KeyConfig { inherit pkgs; } // {
enable = true;
};
temp.cores = 1; # FIXME: should come from NixOps4 eventually
};
}
);
peertube-configuration = makeConfigurationResource peertubeConfigurationResource (
{ pkgs, ... }:
mkIf cfg.peertube.enable {
fediversity = {
inherit (cfg) domain;
temp.initialUser = {
inherit (cfg.initialUser) username email displayName;
# FIXME: disgusting, but nvm, this is going to be replaced by
# proper central authentication at some point
passwordFile = pkgs.writeText "password" cfg.initialUser.password;
};
peertube = peertubeS3KeyConfig { inherit pkgs; } // {
enable = true;
## NOTE: Only ever used for testing anyway.
##
## FIXME: Generate and store in NixOps4's state.
secretsFile = pkgs.writeText "secret" "574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24";
};
};
}
);
pixelfed-configuration = makeConfigurationResource pixelfedConfigurationResource (
{ pkgs, ... }:
mkIf cfg.pixelfed.enable {
fediversity = {
inherit (cfg) domain;
temp.initialUser = {
inherit (cfg.initialUser) username email displayName;
# FIXME: disgusting, but nvm, this is going to be replaced by
# proper central authentication at some point
passwordFile = pkgs.writeText "password" cfg.initialUser.password;
};
pixelfed = pixelfedS3KeyConfig { inherit pkgs; } // {
enable = true;
};
};
}
);
};
};
}

View file

@ -1,45 +1,123 @@
{ inputs, sources, ... }:
{ inputs, self, ... }:
let
allVmIds = builtins.genList (x: 100 + x) 156; # 100 -- 255
makeInstaller = import ./makeInstaller.nix;
in
{
_class = "flake";
flake.nixosConfigurations.provisioning =
let
inherit (builtins) map listToAttrs;
makeProvisioningConfiguration =
vmid:
inputs.nixpkgs.lib.nixosSystem {
modules = [
{ procolix.vmid = vmid; }
./procolixVm.nix
inputs.disko.nixosModules.default
];
};
in
listToAttrs (
map (vmid: {
name = "fedi${toString vmid}";
value = makeProvisioningConfiguration vmid;
}) allVmIds
);
perSystem =
{ pkgs, system, ... }:
flake.isoInstallers.provisioning =
let
inherit (builtins) mapAttrs;
in
mapAttrs (
vmname:
makeInstaller {
inherit (inputs) nixpkgs;
hostKeys = {
rsa = {
private = ./hostKeys/${vmname}/ssh_host_rsa_key;
public = ./hostKeys/${vmname}/ssh_host_rsa_key.pub;
};
ed25519 = {
private = ./hostKeys/${vmname}/ssh_host_ed25519_key;
public = ./hostKeys/${vmname}/ssh_host_ed25519_key.pub;
};
};
}
) self.nixosConfigurations.provisioning;
nixops4Deployments.feditest =
{ providers, ... }:
let
inherit (builtins) readFile;
makeProcolixVmResource = vmid: vmconfig: {
type = providers.local.exec;
imports = [ inputs.nixops4-nixos.modules.nixops4Resource.nixos ];
ssh.opts = "";
ssh.host = "95.215.187.${toString vmid}";
ssh.hostPublicKey = readFile ./hostKeys/fedi${toString vmid}/ssh_host_ed25519_key.pub;
nixpkgs = inputs.nixpkgs;
nixos.module = {
imports = [
vmconfig
{ procolix.vmid = vmid; }
./procolixVm.nix
inputs.snf.nixosModules.fediversity
inputs.disko.nixosModules.default
];
};
};
in
{
checks = {
proxmox-basic = import ./check/proxmox {
inherit (pkgs.testers) runNixOSTest;
inherit sources system;
providers.local = inputs.nixops4-nixos.modules.nixops4Provider.local;
resources = {
fedi100 = makeProcolixVmResource 100 { };
fedi101 = makeProcolixVmResource 101 {
fediversity = {
enable = true;
domain = "fedi101.abundos.eu";
pixelfed.enable = true;
};
};
deployment-basic = import ./check/basic {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
fedi102 = makeProcolixVmResource 102 {
fediversity = {
enable = true;
domain = "fedi102.abundos.eu";
mastodon.enable = true;
temp.cores = 1; # FIXME: should come from NixOps4 eventually
};
};
deployment-cli = import ./check/cli {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
fedi103 = makeProcolixVmResource 103 (
{ pkgs, ... }:
{
fediversity = {
enable = true;
domain = "fedi103.abundos.eu";
peertube.enable = true;
deployment-panel = import ./check/panel {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
temp.peertubeSecretsFile = pkgs.writeText "secret" ''
574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
'';
};
}
);
deployment-model-ssh = import ./check/data-model-ssh {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
deployment-model-nixops4 = import ./check/data-model-nixops4 {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
deployment-model-tf = import ./check/data-model-tf {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
fedi120 = makeProcolixVmResource 120 {
fediversity = {
enable = true;
domain = "fedi120.abundos.eu";
pixelfed.enable = true;
};
};
};
};

View file

@ -1,91 +0,0 @@
/**
Modular function type.
Compared to plain nix functions, adds input type-checks
at the cost of longer stack traces.
Usage:
```nix
{ lib, ... }:
{
options = {
my-function = lib.mkOption {
description = "My type-safe function invocation.";
type = lib.types.submodule PATH/TO/function.nix;
readOnly = true;
default = {
input-type = lib.types.int;
output-type = lib.types.int;
implementation = x: x + x;
};
};
};
config = {
my-function.apply "1"
};
}
```
A sample stack trace using this ends up like:
- `INVOKER.apply.<function body>``
- `function.nix`
- `INVOKER.wrapper.<function body>.output`
- `INVOKER.implementation.<function body>`
*/
{ config, lib, ... }:
let
inherit (lib) mkOption types;
inherit (types)
submodule
functionTo
optionType
;
in
{
options = {
input-type = mkOption {
type = optionType;
};
output-type = mkOption {
type = optionType;
};
function-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo config.output-type;
};
wrapper-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo (submodule {
options = {
input = mkOption {
type = config.input-type;
};
output = mkOption {
type = config.output-type;
};
};
});
};
implementation = mkOption {
type = config.function-type;
default = _: { };
};
wrapper = mkOption {
type = config.wrapper-type;
readOnly = true;
default = input: fn: {
inherit input;
output = config.implementation fn.config.input;
};
};
apply = mkOption {
type = config.function-type;
readOnly = true;
default = input: (config.wrapper input).output;
};
};
}

View file

@ -0,0 +1,7 @@
Host keys
=========
NixOps4 needs the SSH host keys of the machines to work. In the future, it will
handle the deployment and therefore will not be needing our input for this. In
the meantime, we just savagely hardcode the ~250 host keys that we will be
using.

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCjHcbCR3BY3yxRAquVS3F7llJBbCQUeHkopUcwXXkaWMpwZZlCIj2Lb4L+k5r/zZe5eelGfpe66RsfjJZnrP0tVZSJpVN/yZXPH3I8QNbgFvE+8wn3QqGOYQTH5QIbs2rYl2K7HQhIRDcdFuU6ffhuyOxSxXOfORVKNOpevEKB8MDQnpLnFk0fHkYRP0EgojyadTL47o84V3o54QxuKDlormMt4RVkPEr4SFI3HSO2YU07j3YCAHJZ6pFqM7i6ekp/mIwaxVoD30aFAp8zQ10rUbbUUsD6qZYqmMtFYxsm3JVIscxJRxWyabd4y5sjQeZozxeilsKQH5nJPPtAU2dRYmnNvKN62vEDGPQhepsE8oPTGDH2nB4+bU+LsD4Qyc1mC+nsf3I3jIOzOXoaS3msuOyo0HQAsqaAAn7+B6COKP8DV0TKgU02SMmBdX6Ncayiu6Nw25XMZo2uzV2HgNT3ySG+EzS4uhddmD3iPggakw49esZuGmrP+9/uBUtiZ8P7Ws6OZswen6QLuEYCJocjBiHnGonffbr+FFHj3uKht+3G8rQ656YgeInBcXKbHbzx63K7sCLg5BWFyg6kImYPFRdYmXLfqAiaFcdax1Lhl4wIA4n4dloHlJ/xjN4lx63+vUdKSfoaehbyvqx2+1aITagWcqjPvs5IwSpTUWxhmQ== niols@wallace

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2QqbyvntKC6jRp4HNFtMmj34/K3vNz96YmbbzLNFGexi4GSUq9hbmHWi76tNLGLYG+RoHV2AzCF12b2s4c/d3RjoEUVX/krow+Xui3ugCJG3u2t0wAl4rtlqM4wV5cSrsxpDzoRk/YeNX4VqVqv9BXk1wadrc0UcoNT34XKPyR880rTKa1lppCvyckkx2H/lKG8S3mT0z90+/QWEhAkGsaLCuzpBITrUijEMtICZKf6hKzTPRxAOTvjGvZ9caQWmX08vzPR4Heh3pLxlqG47nzWBj00UF4d6sOsTOsiIgDIQJK2FJKGmqyy4iZ9ZPmGSsT6XHJvZUHTpNt4Y7VOreC3Rjb8VtYOjZbNQvzWFsWpK2v41uO+l6yxpsumUQ3mvrVBxZDafADD8/kOw7hwerMk50Ix295lOTharPowySA4EGNPmcTL4f+Dq7nFIemADZWb4DytN+h6C5iMovfxqXe4o14iscBEgSRPa7+ajnha5tZdGSiHM0gNKT9PPwZ8AfZcluvN5hgeNnDa9ZWhBFT1Br5o5YKmCk+HsfnnkNJB/VLOHxrMZ24EBfXUffUU3PJhqUMLaRs3FpbzJjMQlR4siG08OGqkLDb3KSviMOX87uiH55jIm8i6ui3JRI85bSTu15EpjIZnLyc3exGqwWv6hpDFUk+7tFECZFFRMFTQ== niols@wallace

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC63KD3ZgPFMFVDfbINJHTUnmGCQCoP5pDnmK77isKc85aFbBjYQwuxIrCccqhRcN9rl2XWzQTJhDmd2H8RsJ5/Ars0lwvn9byuuiSO5xe+fF+b2DNIlhDEcyJ7TPqf8dS+LEneMXfvPYw6pQ7mdzgxrxkWA27t9Ap5LaWggOvkH1Hi10lXZqrkvP1sFO9vNjQvWsp/i4weZsensnopFMn8b38oa6tSNgedAdEYWSqfYv9jwNP8zTpoVkaD06IOvxeeZzQ+5R254VB9crPyK8uwnfksIj5yyW+SZG4f+Qm4ttiJ9unszHeiNX69toOeRr/KMKHNe3zeGZbhoneEYgHL38mwFo00wtE4bkTvm61OO5Dt99T9HneZTMre12t/NApckcipToASqc8DvQ6Zvun/FrNLGe8d6gBZv2k88ljz1MOXkMhqgsi0EKHn+NQ3MWBaBT9oRfZTqf8Owgg1N9Eu5q10niDSeG93f6UkVmOxegdkTb3UQ4sWf1qDJwt488QhYbnXvpjW6QD/DDXrt2RxM9qKrb6x/byQZnet4HfHTy1OAYNorjs9HXdsu7YXraPsqci7Zr49EhJWGQBmh/7x4Mtvf8IuLHdwfBz5izxstf1hnYgt+zEnJlEz9/D9xruKw4bTUllsmckjX/uhEk41v9giVkBJNsKk8FktoepeWw== niols@wallace

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC1JwAbcsnzJCWywVNUcQ4ii9nW5q/rhG6C6OvPO4hjpJOWmKmKCaJqlvuDWkVRT+hjMr/5LAJFlgnBZ6OAluc04dkv2ffxcLYCqKC7ROZBEhZ2rU+D+tdk1v04/DVHtZ+HOcJZBJmYc6dK2gVwiqj07HvqGLELD8fMDHFYaRimZ9vx3LmQ+v3qaipDP++ZbuRVIuTfE2HL4wK1CTl4xxwEJWAjP/5LYIpi0XImGk6d9R1dsySkayTPpOBW6HFYI4U+gkXuN32+D4KaBlj57jzx06vIlREO/ccJGCkPFkQHWms3wh8UnRi6hLnZOCacAxjLyO7oGX9Ff2CCf3VE6Ga858TyLkWnEZhFZTzcWuvShVKvglxE2mxZ/pR2a1phRNTKIzQG0TAuXuD2LOjUa9BP0HNE4nqhVqTPAk8RYmv6KEtljUwil4WtXUE5hynMNMcjLYJNIM/u32GoRAoJwSHGrv0w56eBfNxdANJtWDxC7Jnbcb9kmEklCwde9XaGfB5vWdzs5XJgk56egD7jQiQKDrsa5/4EYu+me6SbPjOZxrXRQuAnWRUt43I+djyiotjQ6peqIupmgcfbCLGQGcvpTbyRanhHEK59Ay542PUUdFJah7tpXMqA3WGgcMpNG7YnyHsYr3aJKU89VZAv/51ejw1HD325mVnxfCl+tNeKPw== niols@wallace

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAgEAm7dLc3l/DYwCwuvtAIEAUgwrglT8n+3Pl7IOBFpYvxcMuKblpQYr
DVTgSIAlKDZx54QtmgZU2Wj+JWhKTmMPiwG+UtbRvv/8f8VuvOEbpWRCRrf5yWafNJOROy
CIpPZJsshafWSTS3NjaAnq85S5SRW0KNWufF0n2KxqJKYt8D17pUvU/ypoQn3Q1K030ypI
9sa3YUOuV6+uYwBV7iMKSD5BytefzbRB1HU5GgUbvQ2v9le0faO5S+prU6P7RZY7tTKH2B
RcTbkOp+b+C14SKjeUmzKt6sQUexqSlkNJ5ji0EqWkA4gTvr+72E7mZb+X4FPf6wCijhnp
rXFaQCk5p/L2ZbQLavc2dZFCBaJn6bo6Fe9DkoxcWJv4BbNYE0AW0Xfuq2c5ZdupXb8hYN
u2yAKkLuQcrr0uJscSTDgPUbD2CYb7eh6SwSLwIpwua3bRV8NoRM6alea7XfmYEQRgABp/
4Dahe7kglTXblAU0JIoIodZCWBs0COq0lhNB2u48Ii9eUiHlQ5yfBj1NgqamYRCfH8ch94
h2cWsWodyqLEZuCioHzodCNCyIiNxRmZpPaNchMlNEn6OEeV3qONK0Fp9OxUE7vcJnV2W8
bySEOPByZtPqVvmv/XWX42gDOjVH8to+BssmxMrjEfholHy0PhtOLvZitR01cIE6X4oRUC
UAAAdISKH6/Uih+v0AAAAHc3NoLXJzYQAAAgEAm7dLc3l/DYwCwuvtAIEAUgwrglT8n+3P
l7IOBFpYvxcMuKblpQYrDVTgSIAlKDZx54QtmgZU2Wj+JWhKTmMPiwG+UtbRvv/8f8VuvO
EbpWRCRrf5yWafNJOROyCIpPZJsshafWSTS3NjaAnq85S5SRW0KNWufF0n2KxqJKYt8D17
pUvU/ypoQn3Q1K030ypI9sa3YUOuV6+uYwBV7iMKSD5BytefzbRB1HU5GgUbvQ2v9le0fa
O5S+prU6P7RZY7tTKH2BRcTbkOp+b+C14SKjeUmzKt6sQUexqSlkNJ5ji0EqWkA4gTvr+7
2E7mZb+X4FPf6wCijhnprXFaQCk5p/L2ZbQLavc2dZFCBaJn6bo6Fe9DkoxcWJv4BbNYE0
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCbt0tzeX8NjALC6+0AgQBSDCuCVPyf7c+Xsg4EWli/Fwy4puWlBisNVOBIgCUoNnHnhC2aBlTZaP4laEpOYw+LAb5S1tG+//x/xW684RulZEJGt/nJZp80k5E7IIik9kmyyFp9ZJNLc2NoCerzlLlJFbQo1a58XSfYrGokpi3wPXulS9T/KmhCfdDUrTfTKkj2xrdhQ65Xr65jAFXuIwpIPkHK15/NtEHUdTkaBRu9Da/2V7R9o7lL6mtTo/tFlju1MofYFFxNuQ6n5v4LXhIqN5SbMq3qxBR7GpKWQ0nmOLQSpaQDiBO+v7vYTuZlv5fgU9/rAKKOGemtcVpAKTmn8vZltAtq9zZ1kUIFomfpujoV70OSjFxYm/gFs1gTQBbRd+6rZzll26ldvyFg27bIAqQu5ByuvS4mxxJMOA9RsPYJhvt6HpLBIvAinC5rdtFXw2hEzpqV5rtd+ZgRBGAAGn/gNqF7uSCVNduUBTQkigih1kJYGzQI6rSWE0Ha7jwiL15SIeVDnJ8GPU2CpqZhEJ8fxyH3iHZxaxah3KosRm4KKgfOh0I0LIiI3FGZmk9o1yEyU0Sfo4R5Xeo40rQWn07FQTu9wmdXZbxvJIQ48HJm0+pW+a/9dZfjaAM6NUfy2j4GyybEyuMR+GiUfLQ+G04u9mK1HTVwgTpfihFQJQ== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAf+jlWoTpC8KyKdyQLUgKtr7bVzJS8HgKDMIXc7BzJgAAAAJARqo92EaqP
dgAAAAtzc2gtZWQyNTUxOQAAACAf+jlWoTpC8KyKdyQLUgKtr7bVzJS8HgKDMIXc7BzJgA
AAAEAWVnRvRE2d4Us+F96d34qz9x/xiuAzhX4+KBbK+n6aMR/6OVahOkLwrIp3JAtSAq2v
ttXMlLweAoMwhdzsHMmAAAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB/6OVahOkLwrIp3JAtSAq2vttXMlLweAoMwhdzsHMmA

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDVCqZZLq3D+nSkvtqJO1T/kXz7Udg7UVvJEwIrCxsvFCyB50/OznjMP5FDH1dNiKPyF46YvJg3hsUgoffUFVAZxJWYaKh2Nh45aOrg6KDAnV+k9MhMQJnCFonZBLUTx3qtm8r0Uaikd5sJMecZlw/cBeTfQtPKZWGsWDchMGcso6e2Y8YPtrrqId4HTC5z/LTLW+RoU4/hQl2aU/1x1YikoFE5M86pJ1rWMYvJ9olUva0oFE2rhWu+HIbJc+i6FyQ7uer5sw9vWbH9/2TQJrZN8xQuoHFxhDgz75FubKooliE3trVzsxlDUy3rc5URaixq8ZUis/qaL33kwxknAcohDWlHGLVFlNMrGs/pXD6cBjbTnC1wh2iDu4GUjAUjvoGoqI/ObOQcdR3yXN+/fNQ1qZgj375xWQ6m7Pjm6B6y5e93r6h0+Uz6/9tZoXJCTfdLKQpMjD2HUnaJv8gOUJyTTNIm/eeby9pp15anCJA49ELXZFvbXmvhfMC25H3yxFPR0BWrD4EXrGSs3ZTAgb7xSzTU+7huXQX/b3RxFu2TjJEG712BMLS3ECYAUOqeGEEunKDofHDQfiqh9n80YiVejNGJZPWnvUegnMm5cuFqnlsQ4WZxMSHfCSXzADS2+x/ufKKgvsV4gMcdd80UBiE0xyeocpw5lZ6UL3lgTClERw== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDrs4Vbxgu0bWzlOxZDukGmE1GPsd/VXZ3MTfwGdU4BBAAAAJBug7Q3boO0
NwAAAAtzc2gtZWQyNTUxOQAAACDrs4Vbxgu0bWzlOxZDukGmE1GPsd/VXZ3MTfwGdU4BBA
AAAEDNrbC3QiRCpZPhXXnva2FeSPOv9wcLSlEA6EAHCQ0EjeuzhVvGC7RtbOU7FkO6QaYT
UY+x39VdncxN/AZ1TgEEAAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOuzhVvGC7RtbOU7FkO6QaYTUY+x39VdncxN/AZ1TgEE

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCtgbMrwyM36dVFQL8rIH9ceC++hQnDmDziA1RYF06EQVN1adtGKli93DWLNlekJDO2Rvl/GqhmnycnOWTH5xFOkoghlf4OPlY10oJhbw8/l16rpARJtEBJESoWg/VBJFGayeidfjbCW7gypJJYYuFegGPFhMt/ky8L5uOBdw1tegW1+ySM8Hh32Qwkcl1Il18ZYj7HAu8Ay2ciey68hEs4ZXpWLp+ysjmUdhNTY6OCv6cwduL6UjEzgSBPh2IAmLOFabf0rnAV2REFhLqGQ/FgDAzhcbv8VYPZ+WXyxUSwDxxKPz8P3Rp9tRYn99IO5ID7Gze+9TMrYKmPPT4nav4PZaOoauxF2C6tVUz4ZOgBZSqbsWjglxUbQlEN4zRmEqVs4uUYjRaNW5MHzxzUTRM3TeC+prjPEWXhudLpiEt0Q/dnI8l03oR1tLon2o9ufRXWHkRl6ALjEQGPpNpxMzBowLNYrT7+2zEPDbooJHylqIHJdqd0SvvGcIZYBWIGqJ+uvjUYsnhngyqW0vwxPMB9nXK/BdfL1qkJ3X9WiJk8AqWWALoptsz9lPnVcbdwjBzNN0hitsYZlK4Py+f3BLXFUezjRIHNruI6yhO2W0Jv0JdSmj4VN+poS8od+XLIGepowfbygf1rKl/rVLOLDcDlEYvNuzVIPq6V4RQsWEARFw== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCQZTe1DtMsXpseQKXHiLPoA/YyWPi0GA5o5dBPuHyhWgAAAJB10c/YddHP
2AAAAAtzc2gtZWQyNTUxOQAAACCQZTe1DtMsXpseQKXHiLPoA/YyWPi0GA5o5dBPuHyhWg
AAAEB1QyVs3kTY6k5GoPbX6nJJW2fMzPvrXi1MqLGRzX79OZBlN7UO0yxemx5ApceIs+gD
9jJY+LQYDmjl0E+4fKFaAAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBlN7UO0yxemx5ApceIs+gD9jJY+LQYDmjl0E+4fKFa

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDYEA1cgJGP9SYx/a6ixx9nOMeR9PZgGgjWI8r0dNfUat92NPsLVrGFA/u0SAEfKIWX6BtLCXTX2etqfuecYp7g3wY/P1AP3FDzY2TbVgjF9lDn4gSTXxpp/gdGdrw3VFiOKDZdLcpHpa/vdgnzS+y6XvFaXIhb8IDHUL1cKXcTp91ZSOxeAhtqSmk597JRzXAnSVxEAFWKOTOF99JvCd8YPJHdUDXC0G0A0tdcYdfy5NL5G4f2lsRjcCweNycSeC7qjgt617dkSClGsBkA2AtK/vREB/5bdRKGEutFypGewu/EvDyc2WkMHKtglFBds1+utUR90nW08Vrac1hNJ7EwRkr4ABCEnih3grI+SeEz7Ojcd89m7OBE06KQxd4hEPw1AV8NMuxwpIJC6YHZ9wd4vuwA3mzcJ5HwV3W6/irWTgS1zpf2TtknFMxcp8nus9HiB4PNe84K+XY5HBQrXba3ZVR5HIYAlpfJyqtk+pA6APmdpmqLv6DKanxIX2gJbH7qdqaCCqQdTiY20R1s9O2aK/K1oQj8MOrxBiDCU8TIa/aVmzxm3upPXzRaKZiLQdsx545hG0bLO3Y7UQRIdGsGZjTzGyqZu5n6C/Rik0DFcrNWBi2VuahFjkGGkqSgIYi3kP4DnVcq9epyKdNH58hbIuseSu+mNZWnasT1oINlTQ== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCZNT4CQuQlGZqAF2IK8yh3iyW5WLAQ067/c95p3YeTlgAAAJCyC0BysgtA
cgAAAAtzc2gtZWQyNTUxOQAAACCZNT4CQuQlGZqAF2IK8yh3iyW5WLAQ067/c95p3YeTlg
AAAEBADSrMn2eGdljsg96uxQzDhn9rimrABOBY5HuDvsNb5Zk1PgJC5CUZmoAXYgrzKHeL
JblYsBDTrv9z3mndh5OWAAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJk1PgJC5CUZmoAXYgrzKHeLJblYsBDTrv9z3mndh5OW

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpfHm8M3d0CjlQ8iZX3J2QCkpzy0ATwES++nBW3SYlSGEk3p1jxv790mVRUf9nDbz4JLURn2lITujuXtA3ErnH4ChwMLbczPy9m8BqEDpopzej46lIuPz63Wl+4YTAdd8tiPpgGHAbcnA2rXlIQ7716VqTdTKcLnSac2VmbDleKL63wSl1OTVehAPyBVtOaqc8wIE9Kpf2GJ9oRkijGDfmHSzO8TFVgSP9L1ZRM7lQGhGXGkCDDaFu7vdykj6N9/jgqjkPoRdfUlDyPysW5wG8BNcV4UdNHLXS1E4U53m7SJ4knReo807RXzmp8fso9GeOZwvEdJRoRz8bA9sJ+on+I3angFEFsmw1hkEguxeHUQW2nhStqqIvApFPZ2f1zyMBqEFKUiyLib80Q8U2JfZToO9JFRdf+EdMokN/dy0t3Yuy20fhlAPvIbL/Y77lYr72bKXNqyApbnBmZ+tslD/ICq5cgS9oS30HYqNcX6qSS/gwgitq40UURC167ilqafTjHqhYkZwQgCp8YpzuOWLtSNbjY9/OAC5lFnXxQxVynasFypt6rIQdNAmvAb/kAuvyQRrXUEnwxIycFN8KSWV9kRC7z5mTCXTPkIk/I7LOpmMoEJuZeSVbULJcWB+NQ9rpAoOrCVaVRPHz8C015qGcA5QsBxnSdqRKXzUw8N4aGw== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBpmb5FSsRWb0gz344lGP7JByNoPPhrGCo+ml3PBX1ctAAAAJBP2N2BT9jd
gQAAAAtzc2gtZWQyNTUxOQAAACBpmb5FSsRWb0gz344lGP7JByNoPPhrGCo+ml3PBX1ctA
AAAECI5cOILimwJP07ufLW9DGtQ57uencbspqDrSu06a0yeWmZvkVKxFZvSDPfjiUY/skH
I2g8+GsYKj6aXc8FfVy0AAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGmZvkVKxFZvSDPfjiUY/skHI2g8+GsYKj6aXc8FfVy0

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7it/qxNV6U+UF6YYWC+ZWBdCYy9/n/Mf7St2R/VlN5i2rNHWJk8Xz1dLCG2UVx5MiDJw2MZVA0xFxiZ87DHpqtqbLWMLwtYIY1H8q9A8RU5sftTlybHK2/LWssArznkkf+2IqFGYLBxeTdlLqlA9FzxrQD7RmNMtBaskkKAs0EazajifRTvohqpPFisp53lghoJyqFsdNjpIE3WQdMnVUJLFc1FDMTJj7CehjQfiq8fqoRDklXhaCRWILNUxYn9NqLhaOEHG+WSwNQKmcoPI/xmtjgBEfKKWxPNlxrIXx2gTa9EENFkwZ7FeS1LywE8LrKYzOgFbxtZ4W9XMVgNR1wrZqmKTXrGd00sWOj7h+3jeP814Ix156zqrHwUmBtkmUx1pgyGKKi2lTtyNf5qB64shiBu5oVRUuQ5xxKbBUe9vGiKFugQJ1AVY4ySwNxIjhmb5TKYL1qLdPPVq1yGEtTcqVP/+2BZuLDIzuXSuAuGBu0EbVVX4Y7JNWILQ6r+9l7qUMe9MnSTYYbJSkVYwJUSJpBq96qAwlDWHa1WmFwH9fprbA0XWHQGW0s0ev4BOa/kISsS1i5qtbW+uzN1hFXVAtnxwt66SRbRybAHtt8SaKxshmcHQV6X+Dc/6bQZQ5Yk6hl6yCN5TVK2jmYMeBA1Zo8v2mXym2jhF+5ebPzQ== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACA5q6vLiy/HsEJAKgW8A6uVrJtUHrsZaWTmNAaoNBiWPgAAAJDZsKlN2bCp
TQAAAAtzc2gtZWQyNTUxOQAAACA5q6vLiy/HsEJAKgW8A6uVrJtUHrsZaWTmNAaoNBiWPg
AAAECAWAldJQnm4VTXaANpwIOEtTvuLh+rGpRMjmxDZDLUWzmrq8uLL8ewQkAqBbwDq5Ws
m1QeuxlpZOY0Bqg0GJY+AAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDmrq8uLL8ewQkAqBbwDq5Wsm1QeuxlpZOY0Bqg0GJY+

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDTPuFe+GjWrsYJ9+A2W9WXwugamos9k6AWxS0Wgjhg7wj8ogjrg0QuU6w9m2AktRDdFkJ/4zlm0dobzjl+mDsGjjhXrSoJyggvvUnAdWuU6L73GanNPEYnbYaEtvLOcKqv73BzYNYkMDO9j2Q+3tNkjupSPqfwUbLDlGwldeu0yDFeXJUQCr7pCGfszRYN3BiTB1fNp6djcGVV+yiGFqvcer0LNFDcs+SS8GVp0AAEh3brm1crOgOoWjUXnceUic6/lJa0EqeyO0ImUQo7Oi0dqis+oer0ooCPH5Uk5oDmdHcPX7Ir424gjK/q6FWWGXs09ybWCyRI1N0nuU04u470I+EO4k+4RVfuVQOBn6QwEZNdK+m6EwkkGrE0Wv1zQUavNKG7d4vht3oiH7TNAh2qGw/nBiCtyrdEsmcAQWc/BNuGgMcCGqPzlid0ZXWw0ARVTvClthDcacd17RyjMamFaSGYJaDAO9b2Pmcfskbf/B0X7hA06OVDZ2nVAWNZKldeFRX3lK4pwLvwCvhP7a47wYnMJ1CrDQ4zjw/Arga8A318uACV9BPwM/tdajsLy5zfbKtpLhNawJWqZgZPaGHIeDyJYmL8cXgcM3zSlRGJpfQDJxGR4PpmPcE+swYrRrjNPX04aBqnTmLCPHHv2E+XW8Bdf6dwRg78dB4Fhp7AmQ== niols@wallace

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACD5rw87xRY8vYRH8rZ9gwnk1G0u86Ij8AI3oABpATv20QAAAJAh50J9IedC
fQAAAAtzc2gtZWQyNTUxOQAAACD5rw87xRY8vYRH8rZ9gwnk1G0u86Ij8AI3oABpATv20Q
AAAECknxgBlwJbqtcrZBKUVP9nGHIoAWayWWNgvI0kICg/lPmvDzvFFjy9hEfytn2DCeTU
bS7zoiPwAjegAGkBO/bRAAAADW5pb2xzQHdhbGxhY2U=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPmvDzvFFjy9hEfytn2DCeTUbS7zoiPwAjegAGkBO/bR

View file

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCsFz2Ytjs+VLM8Zol6OcG7rHbZkuxyrtJlyrLYbYSDwzBzzcvt5jizJ6+ipppxsOcUwWcUfS5yjZGandwG+dYa6QeflkvFDTdy9L8yiHubZaYcfJbUHHDOAMNW5t0tPjdrNpAgO5mhgzlxYOza2+XyIU6aX6XdxsfxrLwhleMdKdi6ufUYufvv63alhCAyT94dM+QH9uIpp+ZX50eDKlOwbj1vyRPyRQnjHFdKztxQiAyv6AH8TNa2umgTB56w4Zqvfv5DNWoXteZpqzyK/XwKsl9fKfaI5nYOUtlbFEOvO3yq+gufEk7gKGQ/gntu/Etre+SbqfixMc2m7y6fygQbw/x2cLaJdCocHj7tX3r8kml9wjKLXNT3FSHfnXhZ7FrD6lzlAHqgzduq4GZvx5rIDsFjTESRrPqChY17hTQ3Z/m8chcNHnLc8FvlUHyH/ZlPQEs7Mug9YXwX88THJ/wCYtSxbslsfOUBbCi0gRUQox6kMsRN6n2kOh3exLNab+n2g/KZgrxNbtKENug1J7NVNB6iejS2Kg+7X2BOB8NT+BgNfjzr1fjWbHB//by7chilJMKl1FRIfvyb8KSR6UEfVM2Vj4Z77win9MC65rxATtRxpfhhiqpNihTw09iqMpdu8T+yoEh5/tzmZJST39qf0Dj5FhJNfTSH+Y+rMTZ6qw== niols@wallace

Some files were not shown because too many files have changed in this diff Show more