Compare commits
No commits in common. "main" and "main" have entirely different histories.
4
.envrc
4
.envrc
@ -1,13 +1,11 @@
|
|||||||
# shellcheck shell=bash
|
|
||||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
||||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
||||||
fi
|
fi
|
||||||
|
|
||||||
watch_file .direnv/selected-shell
|
watch_file .direnv/selected-shell
|
||||||
watch_file formatter.nix
|
|
||||||
|
|
||||||
if [ -e .direnv/selected-shell ]; then
|
if [ -e .direnv/selected-shell ]; then
|
||||||
use flake ".#$(cat .direnv/selected-shell)"
|
use flake .#$(cat .direnv/selected-shell)
|
||||||
else
|
else
|
||||||
use flake
|
use flake
|
||||||
fi
|
fi
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,10 +1,8 @@
|
|||||||
.direnv
|
.direnv
|
||||||
**/.nixos-test-history
|
|
||||||
***/.hypothesis
|
***/.hypothesis
|
||||||
out.log
|
out.log
|
||||||
.coverage.*
|
.coverage.*
|
||||||
**/qubeclan
|
**/qubeclan
|
||||||
pkgs/repro-hook
|
|
||||||
**/testdir
|
**/testdir
|
||||||
democlan
|
democlan
|
||||||
example_clan
|
example_clan
|
||||||
@ -15,7 +13,6 @@ nixos.qcow2
|
|||||||
**/*.glade~
|
**/*.glade~
|
||||||
/docs/out
|
/docs/out
|
||||||
|
|
||||||
|
|
||||||
# dream2nix
|
# dream2nix
|
||||||
.dream2nix
|
.dream2nix
|
||||||
|
|
||||||
|
@ -19,5 +19,3 @@ Run a local server:
|
|||||||
```shell-session
|
```shell-session
|
||||||
mkdocs serve
|
mkdocs serve
|
||||||
```
|
```
|
||||||
|
|
||||||
Open http://localhost:8000/ in your browser.
|
|
||||||
|
14
README.md
14
README.md
@ -1,6 +1,6 @@
|
|||||||
# Clan core repository
|
# Clan Core Repository
|
||||||
|
|
||||||
Welcome to the Clan core repository, the heart of the [clan.lol](https://clan.lol/) project! This monorepo is the foundation of Clan, a revolutionary open-source project aimed at restoring fun, freedom, and functionality to computing. Here, you'll find all the essential packages, NixOS modules, CLI tools, and tests needed to contribute to and work with the Clan project. Clan leverages the Nix system to ensure reliability, security, and seamless management of digital environments, putting the power back into the hands of users.
|
Welcome to the Clan Core Repository, the heart of the [clan.lol](https://clan.lol/) project! This monorepo is the foundation of Clan, a revolutionary open-source project aimed at restoring fun, freedom, and functionality to computing. Here, you'll find all the essential packages, NixOS modules, CLI tools, and tests needed to contribute to and work with the Clan project. Clan leverages the Nix system to ensure reliability, security, and seamless management of digital environments, putting the power back into the hands of users.
|
||||||
|
|
||||||
## Why Clan?
|
## Why Clan?
|
||||||
|
|
||||||
@ -14,13 +14,13 @@ Our mission is simple: to democratize computing by providing tools that empower
|
|||||||
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
||||||
- **Intuitive Secret Management:** Simplified encryption and password management processes.
|
- **Intuitive Secret Management:** Simplified encryption and password management processes.
|
||||||
|
|
||||||
## Getting started with Clan
|
## Getting Started with Clan
|
||||||
|
|
||||||
If you're new to Clan and eager to dive in, start with our quickstart guide and explore the core functionalities that Clan offers:
|
If you're new to Clan and eager to dive in, start with our quickstart guide and explore the core functionalities that Clan offers:
|
||||||
|
|
||||||
- **Quickstart Guide**: Check out [getting started](https://docs.clan.lol/#starting-with-a-new-clan-project)<!-- [docs/site/index.md](docs/site/index.md) --> to get up and running with Clan in no time.
|
- **Quickstart Guide**: Check out [getting started](https://docs.clan.lol/#starting-with-a-new-clan-project)<!-- [docs/site/index.md](docs/site/index.md) --> to get up and running with Clan in no time.
|
||||||
|
|
||||||
### Managing secrets
|
### Managing Secrets
|
||||||
|
|
||||||
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
|
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
|
||||||
|
|
||||||
@ -32,14 +32,14 @@ The Clan project thrives on community contributions. We welcome everyone to cont
|
|||||||
|
|
||||||
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
|
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
|
||||||
|
|
||||||
## Join the revolution
|
## Join the Revolution
|
||||||
|
|
||||||
Clan is more than a tool; it's a movement towards a better digital future. By contributing to the Clan project, you're part of changing technology for the better, together.
|
Clan is more than a tool; it's a movement towards a better digital future. By contributing to the Clan project, you're part of changing technology for the better, together.
|
||||||
|
|
||||||
### Community and support
|
### Community and Support
|
||||||
|
|
||||||
Connect with us and the Clan community for support and discussion:
|
Connect with us and the Clan community for support and discussion:
|
||||||
|
|
||||||
- [Matrix channel](https://matrix.to/#/#clan:clan.lol) for live discussions.
|
- [Matrix channel](https://matrix.to/#/#clan:lassul.us) for live discussions.
|
||||||
- IRC bridges (coming soon) for real-time chat support.
|
- IRC bridges (coming soon) for real-time chat support.
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
self.clanModules.localbackup
|
self.clanModules.localbackup
|
||||||
self.clanModules.sshd
|
self.clanModules.sshd
|
||||||
];
|
];
|
||||||
clan.core.networking.targetHost = "machine";
|
clan.networking.targetHost = "machine";
|
||||||
networking.hostName = "machine";
|
networking.hostName = "machine";
|
||||||
services.openssh.settings.UseDns = false;
|
services.openssh.settings.UseDns = false;
|
||||||
|
|
||||||
@ -68,9 +68,17 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
clan.core.facts.secretStore = "vm";
|
clanCore.facts.secretStore = "vm";
|
||||||
|
|
||||||
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
environment.systemPackages = [
|
||||||
|
self.packages.${pkgs.system}.clan-cli
|
||||||
|
(pkgs.writeShellScriptBin "pre-restore-command" ''
|
||||||
|
touch /var/test-service/pre-restore-command
|
||||||
|
'')
|
||||||
|
(pkgs.writeShellScriptBin "post-restore-command" ''
|
||||||
|
touch /var/test-service/post-restore-command
|
||||||
|
'')
|
||||||
|
];
|
||||||
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
||||||
nix.settings = {
|
nix.settings = {
|
||||||
substituters = lib.mkForce [ ];
|
substituters = lib.mkForce [ ];
|
||||||
@ -79,18 +87,11 @@
|
|||||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||||
};
|
};
|
||||||
system.extraDependencies = dependencies;
|
system.extraDependencies = dependencies;
|
||||||
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
|
clanCore.state.test-backups.folders = [ "/var/test-backups" ];
|
||||||
|
|
||||||
clan.core.state.test-service = {
|
clanCore.state.test-service = {
|
||||||
preBackupScript = ''
|
preRestoreCommand = "pre-restore-command";
|
||||||
touch /var/test-service/pre-backup-command
|
postRestoreCommand = "post-restore-command";
|
||||||
'';
|
|
||||||
preRestoreScript = ''
|
|
||||||
touch /var/test-service/pre-restore-command
|
|
||||||
'';
|
|
||||||
postRestoreScript = ''
|
|
||||||
touch /var/test-service/post-restore-command
|
|
||||||
'';
|
|
||||||
folders = [ "/var/test-service" ];
|
folders = [ "/var/test-service" ];
|
||||||
};
|
};
|
||||||
clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";
|
clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";
|
||||||
@ -133,7 +134,6 @@
|
|||||||
self.nixosModules.test-backup
|
self.nixosModules.test-backup
|
||||||
];
|
];
|
||||||
virtualisation.emptyDiskImages = [ 256 ];
|
virtualisation.emptyDiskImages = [ 256 ];
|
||||||
clan.core.clanDir = ./.;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
@ -164,15 +164,13 @@
|
|||||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
||||||
machine.succeed("test -f /var/test-service/pre-restore-command")
|
machine.succeed("test -f /var/test-service/pre-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/pre-backup-command")
|
|
||||||
|
|
||||||
## localbackup restore
|
## localbackup restore
|
||||||
machine.succeed("rm -rf /var/test-backups/somefile /var/test-service/ && mkdir -p /var/test-service")
|
machine.succeed("rm -f /var/test-backups/somefile /var/test-service/{pre,post}-restore-command")
|
||||||
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup localbackup '{localbackup_id}' >&2")
|
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup localbackup '{localbackup_id}' >&2")
|
||||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
||||||
machine.succeed("test -f /var/test-service/pre-restore-command")
|
machine.succeed("test -f /var/test-service/pre-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/pre-backup-command")
|
|
||||||
'';
|
'';
|
||||||
} { inherit pkgs self; };
|
} { inherit pkgs self; };
|
||||||
};
|
};
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
clan.core.machineName = "machine";
|
clanCore.machineName = "machine";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
clan.core.state.testState.folders = [ "/etc/state" ];
|
clanCore.state.testState.folders = [ "/etc/state" ];
|
||||||
environment.etc.state.text = "hello world";
|
environment.etc.state.text = "hello world";
|
||||||
systemd.tmpfiles.settings."vmsecrets" = {
|
systemd.tmpfiles.settings."vmsecrets" = {
|
||||||
"/etc/secrets/borgbackup.ssh" = {
|
"/etc/secrets/borgbackup.ssh" = {
|
||||||
@ -36,7 +36,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
clan.core.facts.secretStore = "vm";
|
clanCore.facts.secretStore = "vm";
|
||||||
|
|
||||||
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
|
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
self.clanModules.deltachat
|
self.clanModules.deltachat
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clan.core.machineName = "machine";
|
clanCore.machineName = "machine";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
perSystem =
|
|
||||||
{ self', pkgs, ... }:
|
|
||||||
{
|
|
||||||
checks.devshell =
|
|
||||||
pkgs.runCommand "check-devshell-not-depends-on-clan-cli"
|
|
||||||
{
|
|
||||||
exportReferencesGraph = [
|
|
||||||
"graph"
|
|
||||||
self'.devShells.default
|
|
||||||
];
|
|
||||||
}
|
|
||||||
''
|
|
||||||
if grep -q "${self'.packages.clan-cli}" ./graph; then
|
|
||||||
echo "devshell depends on clan-cli, which is not allowed";
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
mkdir $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,12 +1,10 @@
|
|||||||
{ self, ... }:
|
{ self, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./backups/flake-module.nix
|
|
||||||
./devshell/flake-module.nix
|
|
||||||
./flash/flake-module.nix
|
|
||||||
./impure/flake-module.nix
|
./impure/flake-module.nix
|
||||||
|
./backups/flake-module.nix
|
||||||
./installation/flake-module.nix
|
./installation/flake-module.nix
|
||||||
./nixos-documentation/flake-module.nix
|
./flash/flake-module.nix
|
||||||
];
|
];
|
||||||
perSystem =
|
perSystem =
|
||||||
{
|
{
|
||||||
@ -25,7 +23,7 @@
|
|||||||
options =
|
options =
|
||||||
(pkgs.nixos {
|
(pkgs.nixos {
|
||||||
imports = [ self.nixosModules.clanCore ];
|
imports = [ self.nixosModules.clanCore ];
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
}).options;
|
}).options;
|
||||||
warningsAreErrors = false;
|
warningsAreErrors = false;
|
||||||
};
|
};
|
||||||
@ -42,12 +40,10 @@
|
|||||||
secrets = import ./secrets nixosTestArgs;
|
secrets = import ./secrets nixosTestArgs;
|
||||||
container = import ./container nixosTestArgs;
|
container = import ./container nixosTestArgs;
|
||||||
deltachat = import ./deltachat nixosTestArgs;
|
deltachat = import ./deltachat nixosTestArgs;
|
||||||
borgbackup = import ./borgbackup nixosTestArgs;
|
|
||||||
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
||||||
mumble = import ./mumble nixosTestArgs;
|
|
||||||
syncthing = import ./syncthing nixosTestArgs;
|
|
||||||
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
||||||
postgresql = import ./postgresql nixosTestArgs;
|
borgbackup = import ./borgbackup nixosTestArgs;
|
||||||
|
syncthing = import ./syncthing nixosTestArgs;
|
||||||
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
|
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@
|
|||||||
let
|
let
|
||||||
dependencies = [
|
dependencies = [
|
||||||
pkgs.disko
|
pkgs.disko
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.build.toplevel
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.toplevel
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.build.diskoScript
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.build.diskoScript.drvPath
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript.drvPath
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.clan.deployment.file
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file
|
||||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||||
in
|
in
|
||||||
@ -42,7 +42,7 @@
|
|||||||
testScript = ''
|
testScript = ''
|
||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
machine.succeed("clan flash --debug --flake ${../..} --yes --disk main /dev/vdb test-install-machine")
|
machine.succeed("clan flash --debug --flake ${../..} --yes --disk main /dev/vdb test_install_machine")
|
||||||
'';
|
'';
|
||||||
} { inherit pkgs self; };
|
} { inherit pkgs self; };
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
{
|
{
|
||||||
perSystem =
|
perSystem =
|
||||||
{
|
{ pkgs, lib, ... }:
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
self',
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
# a script that executes all other checks
|
# a script that executes all other checks
|
||||||
packages.impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
packages.impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
||||||
@ -15,21 +10,14 @@
|
|||||||
unset CLAN_DIR
|
unset CLAN_DIR
|
||||||
|
|
||||||
export PATH="${
|
export PATH="${
|
||||||
lib.makeBinPath (
|
lib.makeBinPath [
|
||||||
[
|
|
||||||
pkgs.gitMinimal
|
pkgs.gitMinimal
|
||||||
pkgs.nix
|
pkgs.nix
|
||||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
||||||
]
|
]
|
||||||
++ self'.packages.clan-cli-full.runtimeDependencies
|
|
||||||
)
|
|
||||||
}"
|
}"
|
||||||
ROOT=$(git rev-parse --show-toplevel)
|
ROOT=$(git rev-parse --show-toplevel)
|
||||||
cd "$ROOT/pkgs/clan-cli"
|
cd "$ROOT/pkgs/clan-cli"
|
||||||
|
|
||||||
# this disables dynamic dependency loading in clan-cli
|
|
||||||
export CLAN_NO_DYNAMIC_DEPS=1
|
|
||||||
|
|
||||||
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -s -m impure ./tests $@"
|
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -s -m impure ./tests $@"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
{ self, lib, ... }:
|
{ self, lib, ... }:
|
||||||
{
|
{
|
||||||
clan.machines.test-install-machine = {
|
clan.machines.test_install_machine = {
|
||||||
clan.core.networking.targetHost = "test-install-machine";
|
clan.networking.targetHost = "test_install_machine";
|
||||||
fileSystems."/".device = lib.mkDefault "/dev/vdb";
|
fileSystems."/".device = lib.mkDefault "/dev/vdb";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vdb";
|
boot.loader.grub.device = lib.mkDefault "/dev/vdb";
|
||||||
|
|
||||||
imports = [ self.nixosModules.test-install-machine ];
|
imports = [ self.nixosModules.test_install_machine ];
|
||||||
};
|
};
|
||||||
flake.nixosModules = {
|
flake.nixosModules = {
|
||||||
test-install-machine =
|
test_install_machine =
|
||||||
{ lib, modulesPath, ... }:
|
{ lib, modulesPath, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
self.clanModules.single-disk
|
self.clanModules.disk-layouts
|
||||||
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
||||||
(modulesPath + "/profiles/qemu-guest.nix")
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
];
|
];
|
||||||
clan.single-disk.device = "/dev/vdb";
|
clan.disk-layouts.singleDiskExt4.device = "/dev/vdb";
|
||||||
|
|
||||||
environment.etc."install-successful".text = "ok";
|
environment.etc."install-successful".text = "ok";
|
||||||
|
|
||||||
@ -34,9 +34,9 @@
|
|||||||
let
|
let
|
||||||
dependencies = [
|
dependencies = [
|
||||||
self
|
self
|
||||||
self.nixosConfigurations.test-install-machine.config.system.build.toplevel
|
self.nixosConfigurations.test_install_machine.config.system.build.toplevel
|
||||||
self.nixosConfigurations.test-install-machine.config.system.build.diskoScript
|
self.nixosConfigurations.test_install_machine.config.system.build.diskoScript
|
||||||
self.nixosConfigurations.test-install-machine.config.system.clan.deployment.file
|
self.nixosConfigurations.test_install_machine.config.system.clan.deployment.file
|
||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.nixos-anywhere
|
pkgs.nixos-anywhere
|
||||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
@ -96,12 +96,9 @@
|
|||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
||||||
client.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
||||||
client.succeed("cp -r ${../..} test-flake && chmod -R +w test-flake")
|
|
||||||
client.fail("test -f test-flake/machines/test-install-machine/hardware-configuration.nix")
|
client.succeed("clan machines install --debug --flake ${../..} --yes test_install_machine root@target >&2")
|
||||||
client.succeed("clan machines hw-generate --flake test-flake test-install-machine root@target>&2")
|
|
||||||
client.succeed("test -f test-flake/machines/test-install-machine/hardware-configuration.nix")
|
|
||||||
client.succeed("clan machines install --debug --flake ${../..} --yes test-install-machine root@target >&2")
|
|
||||||
try:
|
try:
|
||||||
target.shutdown()
|
target.shutdown()
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
|
@ -14,8 +14,16 @@ find = {}
|
|||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
test_driver = ["py.typed"]
|
test_driver = ["py.typed"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py311"
|
||||||
|
line-length = 88
|
||||||
|
|
||||||
|
lint.select = [ "E", "F", "I", "U", "N", "RUF", "ANN", "A" ]
|
||||||
|
lint.ignore = ["E501", "ANN101", "ANN401", "A003"]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.12"
|
python_version = "3.11"
|
||||||
warn_redundant_casts = true
|
warn_redundant_casts = true
|
||||||
disallow_untyped_calls = true
|
disallow_untyped_calls = true
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
|
@ -3,17 +3,12 @@ import os
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import types
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_machine_root(machinename: str, root: Path) -> None:
|
def prepare_machine_root(machinename: str, root: Path) -> None:
|
||||||
root.mkdir(parents=True, exist_ok=True)
|
root.mkdir(parents=True, exist_ok=True)
|
||||||
root.joinpath("etc").mkdir(parents=True, exist_ok=True)
|
root.joinpath("etc").mkdir(parents=True, exist_ok=True)
|
||||||
@ -37,8 +32,7 @@ def retry(fn: Callable, timeout: int = 900) -> None:
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
if not fn(True):
|
if not fn(True):
|
||||||
msg = f"action timed out after {timeout} seconds"
|
raise Exception(f"action timed out after {timeout} seconds")
|
||||||
raise Error(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Machine:
|
class Machine:
|
||||||
@ -81,8 +75,7 @@ class Machine:
|
|||||||
if line.startswith("systemd[1]: Startup finished in"):
|
if line.startswith("systemd[1]: Startup finished in"):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
msg = f"Failed to start container {self.name}"
|
raise RuntimeError(f"Failed to start container {self.name}")
|
||||||
raise RuntimeError(msg)
|
|
||||||
childs = (
|
childs = (
|
||||||
Path(f"/proc/{self.process.pid}/task/{self.process.pid}/children")
|
Path(f"/proc/{self.process.pid}/task/{self.process.pid}/children")
|
||||||
.read_text()
|
.read_text()
|
||||||
@ -93,18 +86,16 @@ class Machine:
|
|||||||
), f"Expected exactly one child process for systemd-nspawn, got {childs}"
|
), f"Expected exactly one child process for systemd-nspawn, got {childs}"
|
||||||
try:
|
try:
|
||||||
return int(childs[0])
|
return int(childs[0])
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
msg = f"Failed to parse child process id {childs[0]}"
|
raise RuntimeError(f"Failed to parse child process id {childs[0]}")
|
||||||
raise RuntimeError(msg) from e
|
|
||||||
|
|
||||||
def get_unit_info(self, unit: str) -> dict[str, str]:
|
def get_unit_info(self, unit: str) -> dict[str, str]:
|
||||||
proc = self.systemctl(f'--no-pager show "{unit}"')
|
proc = self.systemctl(f'--no-pager show "{unit}"')
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
msg = (
|
raise Exception(
|
||||||
f'retrieving systemctl info for unit "{unit}"'
|
f'retrieving systemctl info for unit "{unit}"'
|
||||||
f" failed with exit code {proc.returncode}"
|
+ f" failed with exit code {proc.returncode}"
|
||||||
)
|
)
|
||||||
raise Error(msg)
|
|
||||||
|
|
||||||
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
||||||
|
|
||||||
@ -160,7 +151,7 @@ class Machine:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Always run command with shell opts
|
# Always run command with shell opts
|
||||||
command = f"set -eo pipefail; source /etc/profile; set -u; {command}"
|
command = f"set -euo pipefail; {command}"
|
||||||
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
[
|
[
|
||||||
@ -211,16 +202,16 @@ class Machine:
|
|||||||
info = self.get_unit_info(unit)
|
info = self.get_unit_info(unit)
|
||||||
state = info["ActiveState"]
|
state = info["ActiveState"]
|
||||||
if state == "failed":
|
if state == "failed":
|
||||||
msg = f'unit "{unit}" reached state "{state}"'
|
raise Exception(f'unit "{unit}" reached state "{state}"')
|
||||||
raise Error(msg)
|
|
||||||
|
|
||||||
if state == "inactive":
|
if state == "inactive":
|
||||||
proc = self.systemctl("list-jobs --full 2>&1")
|
proc = self.systemctl("list-jobs --full 2>&1")
|
||||||
if "No jobs" in proc.stdout:
|
if "No jobs" in proc.stdout:
|
||||||
info = self.get_unit_info(unit)
|
info = self.get_unit_info(unit)
|
||||||
if info["ActiveState"] == state:
|
if info["ActiveState"] == state:
|
||||||
msg = f'unit "{unit}" is inactive and there are no pending jobs'
|
raise Exception(
|
||||||
raise Error(msg)
|
f'unit "{unit}" is inactive and there are no pending jobs'
|
||||||
|
)
|
||||||
|
|
||||||
return state == "active"
|
return state == "active"
|
||||||
|
|
||||||
@ -229,8 +220,7 @@ class Machine:
|
|||||||
def succeed(self, command: str, timeout: int | None = None) -> str:
|
def succeed(self, command: str, timeout: int | None = None) -> str:
|
||||||
res = self.execute(command, timeout=timeout)
|
res = self.execute(command, timeout=timeout)
|
||||||
if res.returncode != 0:
|
if res.returncode != 0:
|
||||||
msg = f"Failed to run command {command}"
|
raise RuntimeError(f"Failed to run command {command}")
|
||||||
raise RuntimeError(msg)
|
|
||||||
return res.stdout
|
return res.stdout
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
@ -270,8 +260,7 @@ class Driver:
|
|||||||
for container in containers:
|
for container in containers:
|
||||||
name_match = re.match(r".*-nixos-system-(.+)-(.+)", container.name)
|
name_match = re.match(r".*-nixos-system-(.+)-(.+)", container.name)
|
||||||
if not name_match:
|
if not name_match:
|
||||||
msg = f"Unable to extract hostname from {container.name}"
|
raise ValueError(f"Unable to extract hostname from {container.name}")
|
||||||
raise Error(msg)
|
|
||||||
name = name_match.group(1)
|
name = name_match.group(1)
|
||||||
self.machines.append(
|
self.machines.append(
|
||||||
Machine(
|
Machine(
|
||||||
@ -287,12 +276,12 @@ class Driver:
|
|||||||
machine.start()
|
machine.start()
|
||||||
|
|
||||||
def test_symbols(self) -> dict[str, Any]:
|
def test_symbols(self) -> dict[str, Any]:
|
||||||
general_symbols = {
|
general_symbols = dict(
|
||||||
"start_all": self.start_all,
|
start_all=self.start_all,
|
||||||
"machines": self.machines,
|
machines=self.machines,
|
||||||
"driver": self,
|
driver=self,
|
||||||
"Machine": Machine, # for typing
|
Machine=Machine, # for typing
|
||||||
}
|
)
|
||||||
machine_symbols = {pythonize_name(m.name): m for m in self.machines}
|
machine_symbols = {pythonize_name(m.name): m for m in self.machines}
|
||||||
# If there's exactly one machine, make it available under the name
|
# If there's exactly one machine, make it available under the name
|
||||||
# "machine", even if it's not called that.
|
# "machine", even if it's not called that.
|
||||||
@ -300,7 +289,7 @@ class Driver:
|
|||||||
(machine_symbols["machine"],) = self.machines
|
(machine_symbols["machine"],) = self.machines
|
||||||
print(
|
print(
|
||||||
"additionally exposed symbols:\n "
|
"additionally exposed symbols:\n "
|
||||||
+ ", ".join(m.name for m in self.machines)
|
+ ", ".join(map(lambda m: m.name, self.machines))
|
||||||
+ ",\n "
|
+ ",\n "
|
||||||
+ ", ".join(list(general_symbols.keys()))
|
+ ", ".join(list(general_symbols.keys()))
|
||||||
)
|
)
|
||||||
@ -317,12 +306,7 @@ class Driver:
|
|||||||
def __enter__(self) -> "Driver":
|
def __enter__(self) -> "Driver":
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
||||||
self,
|
|
||||||
exc_type: type[BaseException] | None,
|
|
||||||
exc_value: BaseException | None,
|
|
||||||
traceback: types.TracebackType | None,
|
|
||||||
) -> None:
|
|
||||||
for machine in self.machines:
|
for machine in self.machines:
|
||||||
machine.release()
|
machine.release()
|
||||||
|
|
||||||
@ -335,11 +319,9 @@ def writeable_dir(arg: str) -> Path:
|
|||||||
"""
|
"""
|
||||||
path = Path(arg)
|
path = Path(arg)
|
||||||
if not path.is_dir():
|
if not path.is_dir():
|
||||||
msg = f"{path} is not a directory"
|
raise argparse.ArgumentTypeError(f"{path} is not a directory")
|
||||||
raise argparse.ArgumentTypeError(msg)
|
|
||||||
if not os.access(path, os.W_OK):
|
if not os.access(path, os.W_OK):
|
||||||
msg = f"{path} is not a writeable directory"
|
raise argparse.ArgumentTypeError(f"{path} is not a writeable directory")
|
||||||
raise argparse.ArgumentTypeError(msg)
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,65 +4,26 @@
|
|||||||
name = "matrix-synapse";
|
name = "matrix-synapse";
|
||||||
|
|
||||||
nodes.machine =
|
nodes.machine =
|
||||||
{
|
{ self, lib, ... }:
|
||||||
config,
|
|
||||||
self,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
self.clanModules.matrix-synapse
|
self.clanModules.matrix-synapse
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clan.core.machineName = "machine";
|
clanCore.machineName = "machine";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
|
clan.matrix-synapse = {
|
||||||
|
enable = true;
|
||||||
|
domain = "clan.test";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# secret override
|
||||||
|
clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path = "${./synapse-registration_shared_secret}";
|
||||||
services.nginx.virtualHosts."matrix.clan.test" = {
|
services.nginx.virtualHosts."matrix.clan.test" = {
|
||||||
enableACME = lib.mkForce false;
|
enableACME = lib.mkForce false;
|
||||||
forceSSL = lib.mkForce false;
|
forceSSL = lib.mkForce false;
|
||||||
};
|
};
|
||||||
clan.nginx.acme.email = "admins@clan.lol";
|
|
||||||
clan.matrix-synapse = {
|
|
||||||
server_tld = "clan.test";
|
|
||||||
app_domain = "matrix.clan.test";
|
|
||||||
};
|
|
||||||
clan.matrix-synapse.users.admin.admin = true;
|
|
||||||
clan.matrix-synapse.users.someuser = { };
|
|
||||||
|
|
||||||
clan.core.facts.secretStore = "vm";
|
|
||||||
|
|
||||||
# because we use systemd-tmpfiles to copy the secrets, we need to a seperate systemd-tmpfiles call to provison them.
|
|
||||||
boot.postBootCommands = "${config.systemd.package}/bin/systemd-tmpfiles --create /etc/tmpfiles.d/00-vmsecrets.conf";
|
|
||||||
|
|
||||||
systemd.tmpfiles.settings."00-vmsecrets" = {
|
|
||||||
# run before 00-nixos.conf
|
|
||||||
"/etc/secrets" = {
|
|
||||||
d.mode = "0700";
|
|
||||||
z.mode = "0700";
|
|
||||||
};
|
|
||||||
"/etc/secrets/synapse-registration_shared_secret" = {
|
|
||||||
f.argument = "supersecret";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/matrix-password-admin" = {
|
|
||||||
f.argument = "matrix-password1";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/matrix-password-someuser" = {
|
|
||||||
f.argument = "matrix-password2";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@ -71,12 +32,6 @@
|
|||||||
machine.wait_for_unit("matrix-synapse")
|
machine.wait_for_unit("matrix-synapse")
|
||||||
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
||||||
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
|
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
|
||||||
|
|
||||||
machine.systemctl("restart matrix-synapse >&2") # check if user creation is idempotent
|
|
||||||
machine.execute("journalctl -u matrix-synapse --no-pager >&2")
|
|
||||||
machine.wait_for_unit("matrix-synapse")
|
|
||||||
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
|
||||||
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
(import ../lib/test-base.nix) (
|
|
||||||
{ ... }:
|
|
||||||
let
|
|
||||||
common =
|
|
||||||
{ self, pkgs, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.clanModules.mumble
|
|
||||||
self.nixosModules.clanCore
|
|
||||||
(self.inputs.nixpkgs + "/nixos/tests/common/x11.nix")
|
|
||||||
{
|
|
||||||
clan.core.clanDir = ./.;
|
|
||||||
environment.systemPackages = [ pkgs.killall ];
|
|
||||||
services.murmur.sslKey = "/etc/mumble-key";
|
|
||||||
services.murmur.sslCert = "/etc/mumble-cert";
|
|
||||||
clan.core.facts.services.mumble.secret."mumble-key".path = "/etc/mumble-key";
|
|
||||||
clan.core.facts.services.mumble.public."mumble-cert".path = "/etc/mumble-cert";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = "mumble";
|
|
||||||
|
|
||||||
enableOCR = true;
|
|
||||||
|
|
||||||
nodes.peer1 =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
common
|
|
||||||
{
|
|
||||||
clan.core.machineName = "peer1";
|
|
||||||
environment.etc = {
|
|
||||||
"mumble-key".source = ./peer_1/peer_1_test_key;
|
|
||||||
"mumble-cert".source = ./peer_1/peer_1_test_cert;
|
|
||||||
};
|
|
||||||
systemd.tmpfiles.settings."vmsecrets" = {
|
|
||||||
"/etc/secrets/mumble-key" = {
|
|
||||||
C.argument = "${./peer_1/peer_1_test_key}";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "murmur";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/mumble-cert" = {
|
|
||||||
C.argument = "${./peer_1/peer_1_test_cert}";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "murmur";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
services.murmur.sslKey = "/etc/mumble-key";
|
|
||||||
services.murmur.sslCert = "/etc/mumble-cert";
|
|
||||||
clan.core.facts.services.mumble.secret."mumble-key".path = "/etc/mumble-key";
|
|
||||||
clan.core.facts.services.mumble.public."mumble-cert".path = "/etc/mumble-cert";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
nodes.peer2 =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
common
|
|
||||||
{
|
|
||||||
clan.core.machineName = "peer2";
|
|
||||||
environment.etc = {
|
|
||||||
"mumble-key".source = ./peer_2/peer_2_test_key;
|
|
||||||
"mumble-cert".source = ./peer_2/peer_2_test_cert;
|
|
||||||
};
|
|
||||||
systemd.tmpfiles.settings."vmsecrets" = {
|
|
||||||
"/etc/secrets/mumble-key" = {
|
|
||||||
C.argument = "${./peer_2/peer_2_test_key}";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "murmur";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/mumble-cert" = {
|
|
||||||
C.argument = "${./peer_2/peer_2_test_cert}";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "murmur";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
|
|
||||||
with subtest("Waiting for x"):
|
|
||||||
peer1.wait_for_x()
|
|
||||||
peer2.wait_for_x()
|
|
||||||
|
|
||||||
with subtest("Waiting for murmur"):
|
|
||||||
peer1.wait_for_unit("murmur.service")
|
|
||||||
peer2.wait_for_unit("murmur.service")
|
|
||||||
|
|
||||||
with subtest("Starting Mumble"):
|
|
||||||
# starting mumble is blocking
|
|
||||||
peer1.execute("mumble >&2 &")
|
|
||||||
peer2.execute("mumble >&2 &")
|
|
||||||
|
|
||||||
with subtest("Wait for Mumble"):
|
|
||||||
peer1.wait_for_window(r"^Mumble$")
|
|
||||||
peer2.wait_for_window(r"^Mumble$")
|
|
||||||
|
|
||||||
with subtest("Wait for certificate creation"):
|
|
||||||
peer1.wait_for_window(r"^Mumble$")
|
|
||||||
peer1.sleep(3) # mumble is slow to register handlers
|
|
||||||
peer1.send_chars("\n")
|
|
||||||
peer1.send_chars("\n")
|
|
||||||
peer2.wait_for_window(r"^Mumble$")
|
|
||||||
peer2.sleep(3) # mumble is slow to register handlers
|
|
||||||
peer2.send_chars("\n")
|
|
||||||
peer2.send_chars("\n")
|
|
||||||
|
|
||||||
with subtest("Wait for server connect"):
|
|
||||||
peer1.wait_for_window(r"^Mumble Server Connect$")
|
|
||||||
peer2.wait_for_window(r"^Mumble Server Connect$")
|
|
||||||
|
|
||||||
with subtest("Check validity of server certificates"):
|
|
||||||
peer1.execute("killall .mumble-wrapped")
|
|
||||||
peer1.sleep(1)
|
|
||||||
peer1.execute("mumble mumble://peer2 >&2 &")
|
|
||||||
peer1.wait_for_window(r"^Mumble$")
|
|
||||||
peer1.sleep(3) # mumble is slow to register handlers
|
|
||||||
peer1.send_chars("\n")
|
|
||||||
peer1.send_chars("\n")
|
|
||||||
peer1.wait_for_text("Connected.")
|
|
||||||
|
|
||||||
peer2.execute("killall .mumble-wrapped")
|
|
||||||
peer2.sleep(1)
|
|
||||||
peer2.execute("mumble mumble://peer1 >&2 &")
|
|
||||||
peer2.wait_for_window(r"^Mumble$")
|
|
||||||
peer2.sleep(3) # mumble is slow to register handlers
|
|
||||||
peer2.send_chars("\n")
|
|
||||||
peer2.send_chars("\n")
|
|
||||||
peer2.wait_for_text("Connected.")
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,22 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL
|
|
||||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
||||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3
|
|
||||||
MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
|
||||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
|
||||||
AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc
|
|
||||||
R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+
|
|
||||||
7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF
|
|
||||||
vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY
|
|
||||||
aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym
|
|
||||||
gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud
|
|
||||||
DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi
|
|
||||||
oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg
|
|
||||||
F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH
|
|
||||||
5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg
|
|
||||||
TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz
|
|
||||||
DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj
|
|
||||||
i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB
|
|
||||||
ZqMUUkA4DSucb6xxEcTK
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX
|
|
@ -1,14 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICHTCCAaKgAwIBAgIIT2gZuvqVFP0wCgYIKoZIzj0EAwIwSjESMBAGA1UEChMJ
|
|
||||||
U3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdlbmVyYXRlZDESMBAG
|
|
||||||
A1UEAxMJc3luY3RoaW5nMB4XDTIzMTIwNjAwMDAwMFoXDTQzMTIwMTAwMDAwMFow
|
|
||||||
SjESMBAGA1UEChMJU3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdl
|
|
||||||
bmVyYXRlZDESMBAGA1UEAxMJc3luY3RoaW5nMHYwEAYHKoZIzj0CAQYFK4EEACID
|
|
||||||
YgAEBAr1CsciwCa0vi7eC6xxuSGijY3txbjtsyFanec/fge4oJBD3rVpaLKFETb3
|
|
||||||
TvHHsuvblzElcP483MEVq6FMUoxwuL9CzTtpJrRhtwSmAs8AHLFu8irVn8sZjgkL
|
|
||||||
sXMho1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
|
|
||||||
AQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJc3luY3RoaW5nMAoGCCqG
|
|
||||||
SM49BAMCA2kAMGYCMQDbrtLgfcyMMIkNQn+PJe9DHYAqj8C47LQcWuIY/nekhOu0
|
|
||||||
aUfKctEAwyBtI60Y5zcCMQCEdgD/6CNBh7Qqq3z3CKPhlrpxHtCO5tNw17k0jfdH
|
|
||||||
haCwJInHZvZgclHk4EtFpTw=
|
|
||||||
-----END CERTIFICATE-----
|
|
@ -1,6 +0,0 @@
|
|||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MIGkAgEBBDA14Nqo17Xs/xRLGH2KLuyzjKp4eW9iWFobVNM93RZZbECT++W3XcQc
|
|
||||||
cEc5WVtiPmWgBwYFK4EEACKhZANiAAQECvUKxyLAJrS+Lt4LrHG5IaKNje3FuO2z
|
|
||||||
IVqd5z9+B7igkEPetWlosoURNvdO8cey69uXMSVw/jzcwRWroUxSjHC4v0LNO2km
|
|
||||||
tGG3BKYCzwAcsW7yKtWfyxmOCQuxcyE=
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
@ -1,22 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL
|
|
||||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
||||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3
|
|
||||||
MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
|
||||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
|
||||||
AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24
|
|
||||||
AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz
|
|
||||||
j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun
|
|
||||||
ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE
|
|
||||||
pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc
|
|
||||||
gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud
|
|
||||||
DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ
|
|
||||||
NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO
|
|
||||||
7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm
|
|
||||||
uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga
|
|
||||||
TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv
|
|
||||||
pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r
|
|
||||||
RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A
|
|
||||||
Ek4u3+HXZMl5a0LGJ76u
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICHjCCAaOgAwIBAgIJAKbMWefkf1rVMAoGCCqGSM49BAMCMEoxEjAQBgNVBAoT
|
|
||||||
CVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQ
|
|
||||||
BgNVBAMTCXN5bmN0aGluZzAeFw0yMzEyMDYwMDAwMDBaFw00MzEyMDEwMDAwMDBa
|
|
||||||
MEoxEjAQBgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBH
|
|
||||||
ZW5lcmF0ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzB2MBAGByqGSM49AgEGBSuBBAAi
|
|
||||||
A2IABFZTMt4RfsfBue0va7QuNdjfXMI4HfZzJCEcG+b9MtV7FlDmwMKX5fgGykD9
|
|
||||||
FBbC7yiza3+xCobdMb5bakz1qYJ7nUFCv1mwSDo2eNM+/XE+rJmlre8NwkwGmvzl
|
|
||||||
h1uhyqNVMFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
|
||||||
BgEFBQcDAjAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCXN5bmN0aGluZzAKBggq
|
|
||||||
hkjOPQQDAgNpADBmAjEAwzhsroN6R4/quWeXj6dO5gt5CfSTLkLee6vrcuIP5i1U
|
|
||||||
rZvJ3OKQVmmGG6IWYe7iAjEAyuq3X2wznaqiw2YK3IDI4qVeYWpCUap0fwRNq7/x
|
|
||||||
4dC4k+BOzHcuJOwNBIY/bEuK
|
|
||||||
-----END CERTIFICATE-----
|
|
@ -1,6 +0,0 @@
|
|||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MIGkAgEBBDCXHGpvumKjjDRxB6SsjZOb7duw3w+rdlGQCJTIvRThLjD6zwjnyImi
|
|
||||||
7c3PD5nWtLqgBwYFK4EEACKhZANiAARWUzLeEX7HwbntL2u0LjXY31zCOB32cyQh
|
|
||||||
HBvm/TLVexZQ5sDCl+X4BspA/RQWwu8os2t/sQqG3TG+W2pM9amCe51BQr9ZsEg6
|
|
||||||
NnjTPv1xPqyZpa3vDcJMBpr85Ydboco=
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
@ -1 +0,0 @@
|
|||||||
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX
|
|
@ -1,22 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL
|
|
||||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
||||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3
|
|
||||||
MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
|
||||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
|
||||||
AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc
|
|
||||||
R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+
|
|
||||||
7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF
|
|
||||||
vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY
|
|
||||||
aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym
|
|
||||||
gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud
|
|
||||||
DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi
|
|
||||||
oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg
|
|
||||||
F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH
|
|
||||||
5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg
|
|
||||||
TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz
|
|
||||||
DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj
|
|
||||||
i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB
|
|
||||||
ZqMUUkA4DSucb6xxEcTK
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCcdZEJvXJIeOK
|
|
||||||
O5pF5XUFvUeJtCCiwfWvWS662bxcR/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS
|
|
||||||
4zSGQoTEAVzqzVdi3a/gNvsdVLb+7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfd
|
|
||||||
WV1Y5T1tuwc3G8ATrguQ33Uo5vvFvcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIh
|
|
||||||
jMpRG/uZ3u7wtbyZ+WqjsjxZNfnYaMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2Zqh
|
|
||||||
VSYXyDfpAWQFznwKGzD5mjtcyKymgnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4Kn
|
|
||||||
ID30OQW7AgMBAAECggEAGVKn+/Iy+kG+l2cRvV6XseqnoWhjA69M5swviMgIfuAl
|
|
||||||
Xx/boeI4mwoS+dJQKi/0zEbB1MB+gwIDB/0s/vs0vS4MQswBQG/skr+2TmiU+Hgb
|
|
||||||
CF0dIYUZv5rAbScFTumx/mCCqxwc+1QIMzyLKqOYL203EFc92ZJGEVT4th321haZ
|
|
||||||
8Wd+dllcYAb7BbEeBhCrTqRe9T3zt5reZgtZTquTF5hGm8EAyBp6rLjZK7dyZ9dd
|
|
||||||
gyIsDbWgPC9vkRc6x/eANn70hgDbYOuoXwAP/qIFnWLL1Zzy8LKUyOsSgQ91S3S3
|
|
||||||
Il4Lt6lEyU3+61MsCYss7jDoP/7REEjz5h6gfxlFSQKBgQD9u8nhHuwte4/d9VNU
|
|
||||||
rhSBW9h8IJzwPif/eS8vh9VaS2SjR2dDCcHg6rGYKnexeEzUcx56aQMA+p3nRJwy
|
|
||||||
Uwnx5BfEWs9FO6yPR8VEI0a2sBp+hoWKJX/Lvat+QCs6IFuGmlQpczD7/RYAkhG4
|
|
||||||
mwyt/ymqzjukb9mFaeYIltOfPwKBgQDELnkH1ChTUH5u3HgDoelFbzR18okz6dxH
|
|
||||||
urMbfZMAl8W5h2zAvHsAX5qxyHHankOUsiH2y3BrAgqQtTuIA2a5W7j+yHBkYiEZ
|
|
||||||
EUNeI9YNA0KU+wwZpVVvRGUsRB5SUBo5LlcSYmX/V32f0oU5Np44i0vjl3Ju8esx
|
|
||||||
2MLfj1A2hQKBgQDCxtZZZ0h8Pb8Z7wpSFfQNvXi5CLwQvFYuClQLk6VXVErkAJsn
|
|
||||||
XiUjyGYeXnNVm/i2mcyKwXQZ20k90HBrPU2ED8mi5Ob5ya5Uqw6mmMHe2d7sw81d
|
|
||||||
WB37RBWSrCXC0DYSZQQ4cYHn3sd2Fqtd4EBijV7qDLjCKU582OdKLqYzNwKBgH31
|
|
||||||
UKQkJZgIkIThbPT4GewI0GgCRvFb76DmUGUQJTg2Oi86siq1WUwOFiabie5RuxZX
|
|
||||||
oNLyH8W008/BbO2RMX1FVOvRCciJ8LJFkTl6TM6iDzfUUBqPOuFryoG3Yrh60btw
|
|
||||||
81rMbqyZIgFhi0QGu2OWnC0Oadyt2tJwV/5t55R5AoGBAPspZttDmOzVkAJDSn9Z
|
|
||||||
iByYt1KmwBQ6l7LpFg33a7ds9zWqW4+i6r0PzXvSewf/z69L0cAywSk5CaJJjDso
|
|
||||||
dTlNMqwux01wd6V+nQGR871xnsOg+qzgJ565TJZelWgRmNRUooi4DMp5POJA33xp
|
|
||||||
rqAISUfW0w2S+q7/5Lm0QiJE
|
|
||||||
-----END PRIVATE KEY-----
|
|
@ -1,22 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL
|
|
||||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
||||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3
|
|
||||||
MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
|
||||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
|
||||||
AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24
|
|
||||||
AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz
|
|
||||||
j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun
|
|
||||||
ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE
|
|
||||||
pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc
|
|
||||||
gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud
|
|
||||||
DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ
|
|
||||||
NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO
|
|
||||||
7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm
|
|
||||||
uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga
|
|
||||||
TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv
|
|
||||||
pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r
|
|
||||||
RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A
|
|
||||||
Ek4u3+HXZMl5a0LGJ76u
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfP6cZhCs9jOnW
|
|
||||||
qyQP12vrOOxlBrWofYZFf9amUA24AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEv
|
|
||||||
HujKyy8PgcEGP+pwmsfWNQMvU0Dzj3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU
|
|
||||||
1l7fO/OXUlq5kyvIjln7Za4sUHunixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjG
|
|
||||||
G+R6MccH8wwQwmLg5oVBkFEZrnREpnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f
|
|
||||||
8TYGHqbeMQFCKwusnlWPRtrNdaIcgaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO
|
|
||||||
0Bwx8fmRAgMBAAECggEACAkjOnNj5zA0IIP0RuRc6rqtmw9ynTTwUJN51lyVxKI8
|
|
||||||
dQDMEq/S2En+J2VyS7z92/XtbgkBIFx83u7VWl5UWpj2j4UsJFB7IwD7zyiJT4D+
|
|
||||||
+3cM/kX8Wx4XyQZbfbm47N0MXAgFCkn45hxHH0acLReXwmN9wxoDyl7AIjZRdwvG
|
|
||||||
Qq0rnOnIc8kkkew7L6AiFwQS8b77eyzua3d6moKXN9hU/kfiJ6YUFG/WLe0pmQA1
|
|
||||||
HbF27YghfeLnYUt50oDuX6jF6CzQhflchWVq/wn8/cxEpg/RMicWE8ulrTk7o27l
|
|
||||||
JwCrHrhYEBsPuZO4mxX/DHrAMmhTeFjLaV5bQlz0PQKBgQDgRPSOEixYnKz9iPs/
|
|
||||||
EDTlji5LA3Rm6TytRCNsjYY6Trw60KcvYqwyDUCiEjruvOQ9mqgBiQm1VHSalrG3
|
|
||||||
RcbVfpEMouyZbEwmTjS8KdOi5x4Z6AX+4yWDN31jX3b8sktgbxV/HRdg3sA3q7MJ
|
|
||||||
vExTUuoXg57W+FepIZ+XlhSoQwKBgQC1x6UMAlAeW45/yUUm/LFRcCgb/bdCQx+e
|
|
||||||
hSb8w3jdvVoNWgx1j7RsjjFKaZUnseK3qQvVfCm4Qjvlz6MpKDxslaUYuR162Ku0
|
|
||||||
e153z/xc7XRoXyPyPLdGZFlWii30jirB7ZqPdyz6mwlWwqdImNerbUqdFt9R8bId
|
|
||||||
pYsyHB5zmwKBgBjYCq9iW/9E+/TqI8sMpI95fK9app5v4AThs3rnAqOa7Ucmrh6V
|
|
||||||
s7Wnui06D8U6r54Tb+EbqTOpM3Gcl/tRg4FLEA5yTfuA/76Ok1D04Tj+mVsNVPyz
|
|
||||||
dQhgMUe835WGusroA12df2V/x5NjNeYyMdJZMQ2ByyrNQAjAbMmCGq+5AoGBAIj8
|
|
||||||
ERFysMOfxUvg9b7CkDFJrsAhOzew86P2vYGfIHchGTqUkG0LRTDFGrnzxNXsBGjY
|
|
||||||
+DUB40Kajx7IkTETxC0jvA1ceq23l/VjPrZVQt0YiC+a+rCyNn7SYkyHxsfTVr9b
|
|
||||||
ea0BZyDXMntyJrPbkjL6Ik8tDE9pLwuOU84ISJ5fAoGAZ2+Ams/VhdZj/wpRpMky
|
|
||||||
K4jtS4nzbCmJzzTa6vdVV7Kjer5kFxSFFqMrS/FtJ/RxHeHvxdze9dfGu9jIdTKK
|
|
||||||
vSzbyQdHFfZgRkmAKfcoN9u567z7Oc74AQ9UgFEGdEVFQUbfWOevmr8KIPt8nDQK
|
|
||||||
J9HuVfILi1kH0jzDd/64TvA=
|
|
||||||
-----END PRIVATE KEY-----
|
|
@ -1,26 +0,0 @@
|
|||||||
{ self, ... }:
|
|
||||||
let
|
|
||||||
documentationModule = {
|
|
||||||
# This is how some downstream users currently generate documentation
|
|
||||||
# If this breaks notify them via matrix since we spent ~5 hrs for bughunting last time.
|
|
||||||
documentation.nixos.enable = true;
|
|
||||||
documentation.nixos.extraModules = [
|
|
||||||
self.nixosModules.clanCore
|
|
||||||
# This is the only option that is not part of the
|
|
||||||
# module because it is usually set by flake-parts
|
|
||||||
{ clan.core.clanDir = ./.; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
clan = {
|
|
||||||
machines.test-documentation = {
|
|
||||||
# Dummy file system
|
|
||||||
fileSystems."/".device = "/dev/null";
|
|
||||||
boot.loader.grub.device = "/dev/null";
|
|
||||||
imports = [
|
|
||||||
documentationModule
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
(import ../lib/container-test.nix) ({
|
|
||||||
name = "postgresql";
|
|
||||||
|
|
||||||
nodes.machine =
|
|
||||||
{ self, config, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.nixosModules.clanCore
|
|
||||||
self.clanModules.postgresql
|
|
||||||
self.clanModules.localbackup
|
|
||||||
];
|
|
||||||
clan.postgresql.users.test = { };
|
|
||||||
clan.postgresql.databases.test.create.options.OWNER = "test";
|
|
||||||
clan.postgresql.databases.test.restore.stopOnRestore = [ "sample-service" ];
|
|
||||||
clan.localbackup.targets.hdd.directory = "/mnt/external-disk";
|
|
||||||
clan.core.clanDir = ./.;
|
|
||||||
|
|
||||||
systemd.services.sample-service = {
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
script = ''
|
|
||||||
while true; do
|
|
||||||
echo "Hello, world!"
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [ config.services.postgresql.package ];
|
|
||||||
};
|
|
||||||
testScript =
|
|
||||||
{ nodes, ... }:
|
|
||||||
''
|
|
||||||
start_all()
|
|
||||||
machine.wait_for_unit("postgresql")
|
|
||||||
machine.wait_for_unit("sample-service")
|
|
||||||
# Create a test table
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -c 'CREATE TABLE test (id serial PRIMARY KEY);' test")
|
|
||||||
|
|
||||||
machine.succeed("/run/current-system/sw/bin/localbackup-create >&2")
|
|
||||||
timestamp_before = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
|
|
||||||
|
|
||||||
machine.succeed("test -e /mnt/external-disk/snapshot.0/machine/var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'INSERT INTO test DEFAULT VALUES;'")
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'DROP TABLE test;'")
|
|
||||||
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
|
|
||||||
|
|
||||||
machine.succeed("rm -rf /var/backup/postgres")
|
|
||||||
|
|
||||||
machine.succeed("NAME=/mnt/external-disk/snapshot.0 FOLDERS=/var/backup/postgres/test /run/current-system/sw/bin/localbackup-restore >&2")
|
|
||||||
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
|
|
||||||
|
|
||||||
machine.succeed("""
|
|
||||||
set -x
|
|
||||||
${nodes.machine.clan.core.state.test.postRestoreCommand}
|
|
||||||
""")
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -l >&2")
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
|
|
||||||
|
|
||||||
timestamp_after = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
|
|
||||||
assert timestamp_before < timestamp_after, f"{timestamp_before} >= {timestamp_after}: expected sample-service to be restarted after restore"
|
|
||||||
|
|
||||||
# Check that the table is still there
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'SELECT * FROM test;'")
|
|
||||||
output = machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql --csv -c \"SELECT datdba::regrole FROM pg_database WHERE datname = 'test'\"")
|
|
||||||
owner = output.split("\n")[1]
|
|
||||||
assert owner == "test", f"Expected database owner to be 'test', got '{owner}'"
|
|
||||||
|
|
||||||
# check if restore works if the database does not exist
|
|
||||||
machine.succeed("runuser -u postgres -- dropdb test")
|
|
||||||
machine.succeed("${nodes.machine.clan.core.state.test.postRestoreCommand}")
|
|
||||||
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
|
|
||||||
'';
|
|
||||||
})
|
|
@ -10,8 +10,8 @@
|
|||||||
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
|
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
|
||||||
sops.age.keyFile = "/etc/privkey.age";
|
sops.age.keyFile = "/etc/privkey.age";
|
||||||
|
|
||||||
clan.core.clanDir = "${./.}";
|
clanCore.clanDir = "${./.}";
|
||||||
clan.core.machineName = "machine";
|
clanCore.machineName = "machine";
|
||||||
|
|
||||||
networking.hostName = "machine";
|
networking.hostName = "machine";
|
||||||
};
|
};
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
self.clanModules.syncthing
|
self.clanModules.syncthing
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clan.core.machineName = "introducer";
|
clanCore.machineName = "introducer";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
environment.etc = {
|
environment.etc = {
|
||||||
"syncthing.pam".source = ./introducer/introducer_test_cert;
|
"syncthing.pam".source = ./introducer/introducer_test_cert;
|
||||||
"syncthing.key".source = ./introducer/introducer_test_key;
|
"syncthing.key".source = ./introducer/introducer_test_key;
|
||||||
"syncthing.api".source = ./introducer/introducer_test_api;
|
"syncthing.api".source = ./introducer/introducer_test_api;
|
||||||
};
|
};
|
||||||
clan.core.facts.services.syncthing.secret."syncthing.api".path = "/etc/syncthing.api";
|
clanCore.facts.services.syncthing.secret."syncthing.api".path = "/etc/syncthing.api";
|
||||||
services.syncthing.cert = "/etc/syncthing.pam";
|
services.syncthing.cert = "/etc/syncthing.pam";
|
||||||
services.syncthing.key = "/etc/syncthing.key";
|
services.syncthing.key = "/etc/syncthing.key";
|
||||||
# Doesn't test zerotier!
|
# Doesn't test zerotier!
|
||||||
@ -53,8 +53,8 @@
|
|||||||
self.clanModules.syncthing
|
self.clanModules.syncthing
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clan.core.machineName = "peer1";
|
clanCore.machineName = "peer1";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
||||||
builtins.readFile ./introducer/introducer_device_id
|
builtins.readFile ./introducer/introducer_device_id
|
||||||
);
|
);
|
||||||
@ -75,8 +75,8 @@
|
|||||||
self.clanModules.syncthing
|
self.clanModules.syncthing
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clan.core.machineName = "peer2";
|
clanCore.machineName = "peer2";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
||||||
builtins.readFile ./introducer/introducer_device_id
|
builtins.readFile ./introducer/introducer_device_id
|
||||||
);
|
);
|
||||||
|
@ -14,8 +14,8 @@ import ../lib/test-base.nix (
|
|||||||
imports = [
|
imports = [
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clan.core.machineName = "machine";
|
clanCore.machineName = "machine";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
services.wayland-proxy-virtwl.enable = true;
|
services.wayland-proxy-virtwl.enable = true;
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
self.clanModules.zt-tcp-relay
|
self.clanModules.zt-tcp-relay
|
||||||
{
|
{
|
||||||
clan.core.machineName = "machine";
|
clanCore.machineName = "machine";
|
||||||
clan.core.clanDir = ./.;
|
clanCore.clanDir = ./.;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Convenient Administration for the Clan App"
|
|
||||||
categories = ["administration"]
|
|
||||||
---
|
|
@ -1,20 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
{
|
|
||||||
options.clan.admin = {
|
|
||||||
allowedKeys = lib.mkOption {
|
|
||||||
default = { };
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
description = "The allowed public keys for ssh access to the admin user";
|
|
||||||
example = {
|
|
||||||
"key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD...";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
imports = [
|
|
||||||
../sshd
|
|
||||||
../root-password
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues config.clan.admin.allowedKeys;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
{ }
|
|
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Statically configure borgbackup with sane defaults."
|
|
||||||
---
|
|
||||||
This module implements the `borgbackup` backend and implements sane defaults
|
|
||||||
for backup management through `borgbackup` for members of the clan.
|
|
||||||
|
|
||||||
Configure target machines where the backups should be sent to through `targets`.
|
|
||||||
|
|
||||||
Configure machines that should be backuped either through `includeMachines`
|
|
||||||
which will exclusively add the included machines to be backuped, or through
|
|
||||||
`excludeMachines`, which will add every machine except the excluded machine to the backup.
|
|
@ -1,103 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
let
|
|
||||||
clanDir = config.clan.core.clanDir;
|
|
||||||
machineDir = clanDir + "/machines/";
|
|
||||||
in
|
|
||||||
lib.warn
|
|
||||||
"The borgbackup-static module is deprecated use the service via the inventory interface instead."
|
|
||||||
{
|
|
||||||
imports = [ ../borgbackup ];
|
|
||||||
|
|
||||||
options.clan.borgbackup-static = {
|
|
||||||
excludeMachines = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
example = [ config.clan.core.machineName ];
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Machines that should not be backuped.
|
|
||||||
Mutually exclusive with includeMachines.
|
|
||||||
If this is not empty, every other machine except the targets in the clan will be backuped by this module.
|
|
||||||
If includeMachines is set, only the included machines will be backuped.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
includeMachines = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
example = [ config.clan.core.machineName ];
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Machines that should be backuped.
|
|
||||||
Mutually exclusive with excludeMachines.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
targets = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Machines that should act as target machines for backups.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config.services.borgbackup.repos =
|
|
||||||
let
|
|
||||||
machines = builtins.readDir machineDir;
|
|
||||||
borgbackupIpMachinePath = machines: machineDir + machines + "/facts/borgbackup.ssh.pub";
|
|
||||||
filteredMachines =
|
|
||||||
if ((builtins.length config.clan.borgbackup-static.includeMachines) != 0) then
|
|
||||||
lib.filterAttrs (name: _: (lib.elem name config.clan.borgbackup-static.includeMachines)) machines
|
|
||||||
else
|
|
||||||
lib.filterAttrs (name: _: !(lib.elem name config.clan.borgbackup-static.excludeMachines)) machines;
|
|
||||||
machinesMaybeKey = lib.mapAttrsToList (
|
|
||||||
machine: _:
|
|
||||||
let
|
|
||||||
fullPath = borgbackupIpMachinePath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then machine else null
|
|
||||||
) filteredMachines;
|
|
||||||
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
|
|
||||||
hosts = builtins.map (machine: {
|
|
||||||
name = machine;
|
|
||||||
value = {
|
|
||||||
path = "/var/lib/borgbackup/${machine}";
|
|
||||||
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
|
|
||||||
};
|
|
||||||
}) machinesWithKey;
|
|
||||||
in
|
|
||||||
lib.mkIf
|
|
||||||
(builtins.any (
|
|
||||||
target: target == config.clan.core.machineName
|
|
||||||
) config.clan.borgbackup-static.targets)
|
|
||||||
(if (builtins.listToAttrs hosts) != null then builtins.listToAttrs hosts else { });
|
|
||||||
|
|
||||||
config.clan.borgbackup.destinations =
|
|
||||||
let
|
|
||||||
destinations = builtins.map (d: {
|
|
||||||
name = d;
|
|
||||||
value = {
|
|
||||||
repo = "borg@${d}:/var/lib/borgbackup/${config.clan.core.machineName}";
|
|
||||||
};
|
|
||||||
}) config.clan.borgbackup-static.targets;
|
|
||||||
in
|
|
||||||
lib.mkIf (builtins.any (
|
|
||||||
target: target == config.clan.core.machineName
|
|
||||||
) config.clan.borgbackup-static.includeMachines) (builtins.listToAttrs destinations);
|
|
||||||
|
|
||||||
config.assertions = [
|
|
||||||
{
|
|
||||||
assertion =
|
|
||||||
!(
|
|
||||||
((builtins.length config.clan.borgbackup-static.excludeMachines) != 0)
|
|
||||||
&& ((builtins.length config.clan.borgbackup-static.includeMachines) != 0)
|
|
||||||
);
|
|
||||||
message = ''
|
|
||||||
The options:
|
|
||||||
config.clan.borgbackup-static.excludeMachines = [${builtins.toString config.clan.borgbackup-static.excludeMachines}]
|
|
||||||
and
|
|
||||||
config.clan.borgbackup-static.includeMachines = [${builtins.toString config.clan.borgbackup-static.includeMachines}]
|
|
||||||
are mutually exclusive.
|
|
||||||
Use excludeMachines to exclude certain machines and backup the other clan machines.
|
|
||||||
Use include machines to only backup certain machines.
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,13 +1,2 @@
|
|||||||
|
Efficient, deduplicating backup program with optional compression and secure encryption.
|
||||||
---
|
---
|
||||||
description = "Efficient, deduplicating backup program with optional compression and secure encryption."
|
|
||||||
categories = ["backup"]
|
|
||||||
---
|
|
||||||
BorgBackup (short: Borg) gives you:
|
|
||||||
|
|
||||||
- Space efficient storage of backups.
|
|
||||||
- Secure, authenticated encryption.
|
|
||||||
- Compression: lz4, zstd, zlib, lzma or none.
|
|
||||||
- Mountable backups with FUSE.
|
|
||||||
- Easy installation on multiple platforms: Linux, macOS, BSD, ...
|
|
||||||
- Free software (BSD license).
|
|
||||||
- Backed by a large and active open source community.
|
|
@ -6,73 +6,8 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.borgbackup;
|
cfg = config.clan.borgbackup;
|
||||||
preBackupScript = ''
|
|
||||||
declare -A preCommandErrors
|
|
||||||
|
|
||||||
${lib.concatMapStringsSep "\n" (
|
|
||||||
state:
|
|
||||||
lib.optionalString (state.preBackupCommand != null) ''
|
|
||||||
echo "Running pre-backup command for ${state.name}"
|
|
||||||
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
|
|
||||||
preCommandErrors["${state.name}"]=1
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
) (lib.attrValues config.clan.core.state)}
|
|
||||||
|
|
||||||
if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then
|
|
||||||
echo "pre-backup commands failed for the following services:"
|
|
||||||
for state in "''${!preCommandErrors[@]}"; do
|
|
||||||
echo " $state"
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
# Each .nix file in the roles directory is a role
|
|
||||||
# TODO: Helper function to set available roles within module meta.
|
|
||||||
# roles =
|
|
||||||
# if builtins.pathExists ./roles then
|
|
||||||
# lib.pipe ./roles [
|
|
||||||
# builtins.readDir
|
|
||||||
# (lib.filterAttrs (_n: v: v == "regular"))
|
|
||||||
# lib.attrNames
|
|
||||||
# (map (fileName: lib.removeSuffix ".nix" fileName))
|
|
||||||
# ]
|
|
||||||
# else
|
|
||||||
# null;
|
|
||||||
# TODO: make this an interface of every module
|
|
||||||
# Maybe load from readme.md
|
|
||||||
# metaInfoOption = lib.mkOption {
|
|
||||||
# readOnly = true;
|
|
||||||
# description = ''
|
|
||||||
# Meta is used to retrieve information about this module.
|
|
||||||
# - `availableRoles` is a list of roles that can be assigned via the inventory.
|
|
||||||
# - `category` is used to group services in the clan marketplace.
|
|
||||||
# - `description` is a short description of the service for the clan marketplace.
|
|
||||||
# '';
|
|
||||||
# default = {
|
|
||||||
# description = "Borgbackup is a backup program. Optionally, it supports compression and authenticated encryption.";
|
|
||||||
# availableRoles = roles;
|
|
||||||
# category = "backup";
|
|
||||||
# };
|
|
||||||
# type = lib.types.submodule {
|
|
||||||
# options = {
|
|
||||||
# description = lib.mkOption { type = lib.types.str; };
|
|
||||||
# availableRoles = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); };
|
|
||||||
# category = lib.mkOption {
|
|
||||||
# description = "A category for the service. This is used to group services in the clan ui";
|
|
||||||
# type = lib.types.enum [
|
|
||||||
# "backup"
|
|
||||||
# "network"
|
|
||||||
# ];
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
{
|
{
|
||||||
|
|
||||||
# options.clan.borgbackup.meta = metaInfoOption;
|
|
||||||
|
|
||||||
options.clan.borgbackup.destinations = lib.mkOption {
|
options.clan.borgbackup.destinations = lib.mkOption {
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
lib.types.submodule (
|
lib.types.submodule (
|
||||||
@ -91,9 +26,9 @@ in
|
|||||||
rsh = lib.mkOption {
|
rsh = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "ssh -i ${
|
default = "ssh -i ${
|
||||||
config.clan.core.facts.services.borgbackup.secret."borgbackup.ssh".path
|
config.clanCore.facts.services.borgbackup.secret."borgbackup.ssh".path
|
||||||
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
|
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
||||||
defaultText = "ssh -i \${config.clan.core.facts.services.borgbackup.secret.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
defaultText = "ssh -i \${config.clanCore.facts.services.borgbackup.secret.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
||||||
description = "the rsh to use for the backup";
|
description = "the rsh to use for the backup";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -106,16 +41,6 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
options.clan.borgbackup.exclude = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
example = [ "*.pyc" ];
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Directories/Files to exclude from the backup.
|
|
||||||
Use * as a wildcard.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
(lib.mkRemovedOptionModule [
|
(lib.mkRemovedOptionModule [
|
||||||
"clan"
|
"clan"
|
||||||
@ -125,30 +50,21 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
config = lib.mkIf (cfg.destinations != { }) {
|
config = lib.mkIf (cfg.destinations != { }) {
|
||||||
systemd.services = lib.mapAttrs' (
|
|
||||||
_: dest:
|
|
||||||
lib.nameValuePair "borgbackup-job-${dest.name}" {
|
|
||||||
# since borgbackup mounts the system read-only, we need to run in a ExecStartPre script, so we can generate additional files.
|
|
||||||
serviceConfig.ExecStartPre = [
|
|
||||||
''+${pkgs.writeShellScript "borgbackup-job-${dest.name}-pre-backup-commands" preBackupScript}''
|
|
||||||
];
|
|
||||||
}
|
|
||||||
) cfg.destinations;
|
|
||||||
|
|
||||||
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
|
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
|
||||||
paths = lib.unique (
|
paths = lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state));
|
||||||
lib.flatten (map (state: state.folders) (lib.attrValues config.clan.core.state))
|
exclude = [ "*.pyc" ];
|
||||||
);
|
|
||||||
exclude = cfg.exclude;
|
|
||||||
repo = dest.repo;
|
repo = dest.repo;
|
||||||
environment.BORG_RSH = dest.rsh;
|
environment.BORG_RSH = dest.rsh;
|
||||||
compression = "auto,zstd";
|
compression = "auto,zstd";
|
||||||
startAt = "*-*-* 01:00:00";
|
startAt = "*-*-* 01:00:00";
|
||||||
persistentTimer = true;
|
persistentTimer = true;
|
||||||
|
preHook = ''
|
||||||
|
set -x
|
||||||
|
'';
|
||||||
|
|
||||||
encryption = {
|
encryption = {
|
||||||
mode = "repokey";
|
mode = "repokey";
|
||||||
passCommand = "cat ${config.clan.core.facts.services.borgbackup.secret."borgbackup.repokey".path}";
|
passCommand = "cat ${config.clanCore.facts.services.borgbackup.secret."borgbackup.repokey".path}";
|
||||||
};
|
};
|
||||||
|
|
||||||
prune.keep = {
|
prune.keep = {
|
||||||
@ -159,7 +75,7 @@ in
|
|||||||
};
|
};
|
||||||
}) cfg.destinations;
|
}) cfg.destinations;
|
||||||
|
|
||||||
clan.core.facts.services.borgbackup = {
|
clanCore.facts.services.borgbackup = {
|
||||||
public."borgbackup.ssh.pub" = { };
|
public."borgbackup.ssh.pub" = { };
|
||||||
secret."borgbackup.ssh" = { };
|
secret."borgbackup.ssh" = { };
|
||||||
secret."borgbackup.repokey" = { };
|
secret."borgbackup.repokey" = { };
|
||||||
@ -195,7 +111,7 @@ in
|
|||||||
(pkgs.writeShellScriptBin "borgbackup-restore" ''
|
(pkgs.writeShellScriptBin "borgbackup-restore" ''
|
||||||
set -efux
|
set -efux
|
||||||
cd /
|
cd /
|
||||||
IFS=':' read -ra FOLDER <<< "$FOLDERS"
|
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
||||||
job_name=$(echo "$NAME" | ${pkgs.gawk}/bin/awk -F'::' '{print $1}')
|
job_name=$(echo "$NAME" | ${pkgs.gawk}/bin/awk -F'::' '{print $1}')
|
||||||
backup_name=''${NAME#"$job_name"::}
|
backup_name=''${NAME#"$job_name"::}
|
||||||
if ! command -v borg-job-"$job_name" &> /dev/null; then
|
if ! command -v borg-job-"$job_name" &> /dev/null; then
|
||||||
@ -206,7 +122,7 @@ in
|
|||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
clan.core.backups.providers.borgbackup = {
|
clanCore.backups.providers.borgbackup = {
|
||||||
list = "borgbackup-list";
|
list = "borgbackup-list";
|
||||||
create = "borgbackup-create";
|
create = "borgbackup-create";
|
||||||
restore = "borgbackup-restore";
|
restore = "borgbackup-restore";
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
let
|
|
||||||
instances = config.clan.inventory.services.borgbackup;
|
|
||||||
# roles = { ${role_name} :: { machines :: [string] } }
|
|
||||||
allServers = lib.foldlAttrs (
|
|
||||||
acc: _instanceName: instanceConfig:
|
|
||||||
acc
|
|
||||||
++ (
|
|
||||||
if builtins.elem machineName instanceConfig.roles.client.machines then
|
|
||||||
instanceConfig.roles.server.machines
|
|
||||||
else
|
|
||||||
[ ]
|
|
||||||
)
|
|
||||||
) [ ] instances;
|
|
||||||
|
|
||||||
inherit (config.clan.core) machineName;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config.clan.borgbackup.destinations =
|
|
||||||
let
|
|
||||||
|
|
||||||
destinations = builtins.map (serverName: {
|
|
||||||
name = serverName;
|
|
||||||
value = {
|
|
||||||
repo = "borg@${serverName}:/var/lib/borgbackup/${machineName}";
|
|
||||||
};
|
|
||||||
}) allServers;
|
|
||||||
in
|
|
||||||
(builtins.listToAttrs destinations);
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
let
|
|
||||||
clanDir = config.clan.core.clanDir;
|
|
||||||
machineDir = clanDir + "/machines/";
|
|
||||||
inherit (config.clan.core) machineName;
|
|
||||||
|
|
||||||
instances = config.clan.inventory.services.borgbackup;
|
|
||||||
|
|
||||||
# roles = { ${role_name} :: { machines :: [string] } }
|
|
||||||
|
|
||||||
allClients = lib.foldlAttrs (
|
|
||||||
acc: _instanceName: instanceConfig:
|
|
||||||
acc
|
|
||||||
++ (
|
|
||||||
if (builtins.elem machineName instanceConfig.roles.server.machines) then
|
|
||||||
instanceConfig.roles.client.machines
|
|
||||||
else
|
|
||||||
[ ]
|
|
||||||
)
|
|
||||||
) [ ] instances;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config.services.borgbackup.repos =
|
|
||||||
let
|
|
||||||
borgbackupIpMachinePath = machines: machineDir + machines + "/facts/borgbackup.ssh.pub";
|
|
||||||
machinesMaybeKey = builtins.map (
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
fullPath = borgbackupIpMachinePath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then
|
|
||||||
machine
|
|
||||||
else
|
|
||||||
lib.warn ''
|
|
||||||
Machine ${machine} does not have a borgbackup key at ${fullPath},
|
|
||||||
run `clan facts generate ${machine}` to generate it.
|
|
||||||
'' null
|
|
||||||
) allClients;
|
|
||||||
|
|
||||||
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
|
|
||||||
|
|
||||||
hosts = builtins.map (machine: {
|
|
||||||
name = machine;
|
|
||||||
value = {
|
|
||||||
path = "/var/lib/borgbackup/${machine}";
|
|
||||||
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
|
|
||||||
};
|
|
||||||
}) machinesWithKey;
|
|
||||||
in
|
|
||||||
if (builtins.listToAttrs hosts) != [ ] then builtins.listToAttrs hosts else { };
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
---
|
Email-based instant messaging for Desktop.
|
||||||
description = "Email-based instant messaging for Desktop."
|
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! warning "Under construction"
|
!!! warning "Under construction"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
services.maddy =
|
services.maddy =
|
||||||
let
|
let
|
||||||
domain = "${config.clan.core.machineName}.local";
|
domain = "${config.clanCore.machineName}.local";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
|
2
clanModules/disk-layouts/README.md
Normal file
2
clanModules/disk-layouts/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Automatically format a disk drive on clan installation
|
||||||
|
---
|
@ -1,12 +1,9 @@
|
|||||||
{ lib, config, ... }:
|
{ config, lib, ... }:
|
||||||
{
|
{
|
||||||
options.clan.single-disk = {
|
options.clan.disk-layouts.singleDiskExt4 = {
|
||||||
device = lib.mkOption {
|
device = lib.mkOption {
|
||||||
default = null;
|
type = lib.types.str;
|
||||||
type = lib.types.nullOr lib.types.str;
|
example = "/dev/disk/by-id/ata-Samsung_SSD_850_EVO_250GB_S21PNXAGB12345";
|
||||||
description = "The primary disk device to install the system on";
|
|
||||||
# Question: should we set a default here?
|
|
||||||
# default = "/dev/null";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
@ -16,18 +13,16 @@
|
|||||||
disk = {
|
disk = {
|
||||||
main = {
|
main = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
# This is set through the UI
|
device = config.clan.disk-layouts.singleDiskExt4.device;
|
||||||
device = config.clan.single-disk.device;
|
|
||||||
|
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
"${config.networking.hostName}-boot" = {
|
boot = {
|
||||||
size = "1M";
|
size = "1M";
|
||||||
type = "EF02"; # for grub MBR
|
type = "EF02"; # for grub MBR
|
||||||
priority = 1;
|
priority = 1;
|
||||||
};
|
};
|
||||||
"${config.networking.hostName}-ESP" = {
|
ESP = {
|
||||||
size = "512M";
|
size = "512M";
|
||||||
type = "EF00";
|
type = "EF00";
|
||||||
content = {
|
content = {
|
||||||
@ -36,7 +31,7 @@
|
|||||||
mountpoint = "/boot";
|
mountpoint = "/boot";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"${config.networking.hostName}-root" = {
|
root = {
|
||||||
size = "100%";
|
size = "100%";
|
||||||
content = {
|
content = {
|
||||||
type = "filesystem";
|
type = "filesystem";
|
@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A dynamic DNS service to update domain IPs"
|
|
||||||
---
|
|
||||||
|
|
||||||
To understand the possible options that can be set visit the documentation of [ddns-updater](https://github.com/qdm12/ddns-updater?tab=readme-ov-file#versioned-documentation)
|
|
||||||
|
|
@ -1,243 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
name = "dyndns";
|
|
||||||
cfg = config.clan.${name};
|
|
||||||
|
|
||||||
# We dedup secrets if they have the same provider + base domain
|
|
||||||
secret_id = opt: "${name}-${opt.provider}-${opt.domain}";
|
|
||||||
secret_path =
|
|
||||||
opt: config.clan.core.facts.services."${secret_id opt}".secret."${secret_id opt}".path;
|
|
||||||
|
|
||||||
# We check that a secret has not been set in extraSettings.
|
|
||||||
extraSettingsSafe =
|
|
||||||
opt:
|
|
||||||
if (builtins.hasAttr opt.secret_field_name opt.extraSettings) then
|
|
||||||
throw "Please do not set ${opt.secret_field_name} in extraSettings, it is automatically set by the dyndns module."
|
|
||||||
else
|
|
||||||
opt.extraSettings;
|
|
||||||
/*
|
|
||||||
We go from:
|
|
||||||
{home.example.com:{value:{domain:example.com,host:home, provider:namecheap}}}
|
|
||||||
To:
|
|
||||||
{settings: [{domain: example.com, host: home, provider: namecheap, password: dyndns-namecheap-example.com}]}
|
|
||||||
*/
|
|
||||||
service_config = {
|
|
||||||
settings = builtins.catAttrs "value" (
|
|
||||||
builtins.attrValues (
|
|
||||||
lib.mapAttrs (_: opt: {
|
|
||||||
value =
|
|
||||||
(extraSettingsSafe opt)
|
|
||||||
// {
|
|
||||||
domain = opt.domain;
|
|
||||||
provider = opt.provider;
|
|
||||||
}
|
|
||||||
// {
|
|
||||||
"${opt.secret_field_name}" = secret_id opt;
|
|
||||||
};
|
|
||||||
}) cfg.settings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
secret_generator = _: opt: {
|
|
||||||
name = secret_id opt;
|
|
||||||
value = {
|
|
||||||
secret.${secret_id opt} = { };
|
|
||||||
generator.prompt = "Dyndns passphrase for ${secret_id opt}";
|
|
||||||
generator.script = ''
|
|
||||||
echo "$prompt_value" > $secrets/${secret_id opt}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.${name} = {
|
|
||||||
server = {
|
|
||||||
enable = lib.mkEnableOption "dyndns webserver";
|
|
||||||
domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Domain to serve the webservice on";
|
|
||||||
};
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 54805;
|
|
||||||
description = "Port to listen on";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
period = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 5;
|
|
||||||
description = "Domain update period in minutes";
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
provider = lib.mkOption {
|
|
||||||
example = "namecheap";
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The dyndns provider to use";
|
|
||||||
};
|
|
||||||
domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
example = "example.com";
|
|
||||||
description = "The top level domain to update.";
|
|
||||||
};
|
|
||||||
secret_field_name = lib.mkOption {
|
|
||||||
example = [
|
|
||||||
"password"
|
|
||||||
"api_key"
|
|
||||||
];
|
|
||||||
type = lib.types.enum [
|
|
||||||
"password"
|
|
||||||
"token"
|
|
||||||
"api_key"
|
|
||||||
];
|
|
||||||
default = "password";
|
|
||||||
description = "The field name for the secret";
|
|
||||||
};
|
|
||||||
# TODO: Ideally we would create a gigantic list of all possible settings / types
|
|
||||||
# optimally we would have a way to generate the options from the source code
|
|
||||||
extraSettings = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
description = ''
|
|
||||||
Extra settings for the provider.
|
|
||||||
Provider specific settings: https://github.com/qdm12/ddns-updater#configuration
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default = [ ];
|
|
||||||
description = "Configuration for which domains to update";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
../nginx
|
|
||||||
];
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
|
||||||
(lib.mkIf (cfg.settings != { }) {
|
|
||||||
clan.core.facts.services = lib.mapAttrs' secret_generator cfg.settings;
|
|
||||||
|
|
||||||
users.groups.${name} = { };
|
|
||||||
users.users.${name} = {
|
|
||||||
group = name;
|
|
||||||
isSystemUser = true;
|
|
||||||
description = "User for ${name} service";
|
|
||||||
home = "/var/lib/${name}";
|
|
||||||
createHome = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx = lib.mkIf cfg.server.enable {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts = {
|
|
||||||
"${cfg.server.domain}" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://localhost:${toString cfg.server.port}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.${name} = {
|
|
||||||
path = [ ];
|
|
||||||
description = "Dynamic DNS updater";
|
|
||||||
after = [ "network.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
environment = {
|
|
||||||
MYCONFIG = "${builtins.toJSON service_config}";
|
|
||||||
SERVER_ENABLED = if cfg.server.enable then "yes" else "no";
|
|
||||||
PERIOD = "${toString cfg.period}m";
|
|
||||||
LISTENING_ADDRESS = ":${toString cfg.server.port}";
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceConfig =
|
|
||||||
let
|
|
||||||
pyscript = pkgs.writers.writePyPy3Bin "test.py" { libraries = [ ]; } ''
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
|
|
||||||
cred_dir = Path(os.getenv("CREDENTIALS_DIRECTORY"))
|
|
||||||
config_str = os.getenv("MYCONFIG")
|
|
||||||
|
|
||||||
|
|
||||||
def get_credential(name):
|
|
||||||
secret_p = cred_dir / name
|
|
||||||
with open(secret_p, 'r') as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
|
|
||||||
config = json.loads(config_str)
|
|
||||||
print(f"Config: {config}")
|
|
||||||
for attrset in config["settings"]:
|
|
||||||
if "password" in attrset:
|
|
||||||
attrset['password'] = get_credential(attrset['password'])
|
|
||||||
elif "token" in attrset:
|
|
||||||
attrset['token'] = get_credential(attrset['token'])
|
|
||||||
elif "api_key" in attrset:
|
|
||||||
attrset['api_key'] = get_credential(attrset['api_key'])
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Missing secret field in {attrset}")
|
|
||||||
|
|
||||||
# create directory data if it does not exist
|
|
||||||
data_dir = Path('data')
|
|
||||||
data_dir.mkdir(mode=0o770, exist_ok=True)
|
|
||||||
|
|
||||||
# Write the config with secrets back
|
|
||||||
config_path = data_dir / 'config.json'
|
|
||||||
with open(config_path, 'w') as f:
|
|
||||||
f.write(json.dumps(config, indent=4))
|
|
||||||
|
|
||||||
# Set file permissions to read and write
|
|
||||||
# only by the user and group
|
|
||||||
config_path.chmod(0o660)
|
|
||||||
|
|
||||||
# Set file permissions to read
|
|
||||||
# and write only by the user and group
|
|
||||||
for file in data_dir.iterdir():
|
|
||||||
file.chmod(0o660)
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
ExecStartPre = lib.getExe pyscript;
|
|
||||||
ExecStart = lib.getExe pkgs.ddns-updater;
|
|
||||||
LoadCredential = lib.mapAttrsToList (_: opt: "${secret_id opt}:${secret_path opt}") cfg.settings;
|
|
||||||
User = name;
|
|
||||||
Group = name;
|
|
||||||
NoNewPrivileges = true;
|
|
||||||
PrivateTmp = true;
|
|
||||||
ProtectSystem = "strict";
|
|
||||||
ReadOnlyPaths = "/";
|
|
||||||
PrivateDevices = "yes";
|
|
||||||
ProtectKernelModules = "yes";
|
|
||||||
ProtectKernelTunables = "yes";
|
|
||||||
WorkingDirectory = "/var/lib/${name}";
|
|
||||||
ReadWritePaths = [
|
|
||||||
"/proc/self"
|
|
||||||
"/var/lib/${name}"
|
|
||||||
];
|
|
||||||
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = 60;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,3 +1,2 @@
|
|||||||
---
|
A modern IRC server
|
||||||
description = "A modern IRC server"
|
|
||||||
---
|
---
|
||||||
|
@ -10,5 +10,5 @@ _: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clan.core.state.ergochat.folders = [ "/var/lib/ergo" ];
|
clanCore.state.ergochat.folders = [ "/var/lib/ergo" ];
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,24 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
flake.clanModules = {
|
flake.clanModules = {
|
||||||
admin = ./admin;
|
disk-layouts = {
|
||||||
|
imports = [ ./disk-layouts ];
|
||||||
|
};
|
||||||
borgbackup = ./borgbackup;
|
borgbackup = ./borgbackup;
|
||||||
borgbackup-static = ./borgbackup-static;
|
|
||||||
deltachat = ./deltachat;
|
deltachat = ./deltachat;
|
||||||
dyndns = ./dyndns;
|
|
||||||
ergochat = ./ergochat;
|
ergochat = ./ergochat;
|
||||||
garage = ./garage;
|
|
||||||
golem-provider = ./golem-provider;
|
|
||||||
heisenbridge = ./heisenbridge;
|
|
||||||
iwd = ./iwd;
|
|
||||||
localbackup = ./localbackup;
|
localbackup = ./localbackup;
|
||||||
localsend = ./localsend;
|
localsend = ./localsend;
|
||||||
matrix-synapse = ./matrix-synapse;
|
matrix-synapse = ./matrix-synapse;
|
||||||
moonlight = ./moonlight;
|
moonlight = ./moonlight;
|
||||||
mumble = ./mumble;
|
|
||||||
packages = ./packages;
|
|
||||||
nginx = ./nginx;
|
|
||||||
postgresql = ./postgresql;
|
|
||||||
root-password = ./root-password;
|
root-password = ./root-password;
|
||||||
single-disk = ./single-disk;
|
|
||||||
sshd = ./sshd;
|
sshd = ./sshd;
|
||||||
static-hosts = ./static-hosts;
|
|
||||||
sunshine = ./sunshine;
|
sunshine = ./sunshine;
|
||||||
|
static-hosts = ./static-hosts;
|
||||||
syncthing = ./syncthing;
|
syncthing = ./syncthing;
|
||||||
syncthing-static-peers = ./syncthing-static-peers;
|
|
||||||
thelounge = ./thelounge;
|
thelounge = ./thelounge;
|
||||||
trusted-nix-caches = ./trusted-nix-caches;
|
trusted-nix-caches = ./trusted-nix-caches;
|
||||||
user-password = ./user-password;
|
user-password = ./user-password;
|
||||||
vaultwarden = ./vaultwarden;
|
|
||||||
xfce = ./xfce;
|
xfce = ./xfce;
|
||||||
zerotier-static-peers = ./zerotier-static-peers;
|
zerotier-static-peers = ./zerotier-static-peers;
|
||||||
zt-tcp-relay = ./zt-tcp-relay;
|
zt-tcp-relay = ./zt-tcp-relay;
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
description = "S3-compatible object store for small self-hosted geo-distributed deployments"
|
|
||||||
---
|
|
||||||
|
|
||||||
This module generates garage specific keys automatically.
|
|
||||||
When using garage in a distributed deployment the `rpc_key` between connected instances must be shared.
|
|
||||||
This is currently still a manual process.
|
|
||||||
|
|
||||||
Options: [NixosModuleOptions](https://search.nixos.org/options?channel=unstable&size=50&sort=relevance&type=packages&query=garage)
|
|
||||||
Documentation: https://garagehq.deuxfleurs.fr/
|
|
@ -1,50 +0,0 @@
|
|||||||
{ config, pkgs, ... }:
|
|
||||||
{
|
|
||||||
systemd.services.garage.serviceConfig = {
|
|
||||||
LoadCredential = [
|
|
||||||
"rpc_secret_path:${config.clan.core.facts.services.garage.secret.garage_rpc_secret.path}"
|
|
||||||
"admin_token_path:${config.clan.core.facts.services.garage.secret.garage_admin_token.path}"
|
|
||||||
"metrics_token_path:${config.clan.core.facts.services.garage.secret.garage_metrics_token.path}"
|
|
||||||
];
|
|
||||||
Environment = [
|
|
||||||
"GARAGE_ALLOW_WORLD_READABLE_SECRETS=true"
|
|
||||||
"GARAGE_RPC_SECRET_FILE=%d/rpc_secret_path"
|
|
||||||
"GARAGE_ADMIN_TOKEN_FILE=%d/admin_token_path"
|
|
||||||
"GARAGE_METRICS_TOKEN_FILE=%d/metrics_token_path"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.facts.services.garage = {
|
|
||||||
secret.garage_rpc_secret = { };
|
|
||||||
secret.garage_admin_token = { };
|
|
||||||
secret.garage_metrics_token = { };
|
|
||||||
generator.path = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssl
|
|
||||||
];
|
|
||||||
generator.script = ''
|
|
||||||
openssl rand -hex -out $secrets/garage_rpc_secret 32
|
|
||||||
openssl rand -base64 -out $secrets/garage_admin_token 32
|
|
||||||
openssl rand -base64 -out $secrets/garage_metrics_token 32
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# TODO: Vars is not in a useable state currently
|
|
||||||
# Move back, once it is implemented.
|
|
||||||
# clan.core.vars.generators.garage = {
|
|
||||||
# files.rpc_secret = { };
|
|
||||||
# files.admin_token = { };
|
|
||||||
# files.metrics_token = { };
|
|
||||||
# runtimeInputs = [
|
|
||||||
# pkgs.coreutils
|
|
||||||
# pkgs.openssl
|
|
||||||
# ];
|
|
||||||
# script = ''
|
|
||||||
# openssl rand -hex -out $out/rpc_secret 32
|
|
||||||
# openssl rand -base64 -out $out/admin_token 32
|
|
||||||
# openssl rand -base64 -out $out/metrics_token 32
|
|
||||||
# '';
|
|
||||||
# };
|
|
||||||
|
|
||||||
clan.core.state.garage.folders = [ config.services.garage.settings.metadata_dir ];
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Golem Provider for the Golem Network, an open-source and decentralized platform where everyone can use and share each other's computing power without relying on centralized entities like cloud computing corporations"
|
|
||||||
---
|
|
||||||
|
|
||||||
By running a golem provider your machine's compute resources are offered via the golem network which will allow other members to execute compute tasks on your machine. If this happens, you will be compensated with GLM, an ERC20 token.
|
|
||||||
|
|
||||||
More about golem providers: https://docs.golem.network/docs/golem/overview
|
|
@ -1,34 +0,0 @@
|
|||||||
{ config, pkgs, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.clan.golem-provider;
|
|
||||||
yagna = pkgs.callPackage ../../pkgs/yagna { };
|
|
||||||
accountFlag = if cfg.account != null then "--account ${cfg.account}" else "";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ./interface.nix ];
|
|
||||||
|
|
||||||
users.users.golem = {
|
|
||||||
isSystemUser = true;
|
|
||||||
home = "/var/lib/golem";
|
|
||||||
group = "golem";
|
|
||||||
createHome = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.golem = { };
|
|
||||||
|
|
||||||
environment.systemPackages = [ yagna ];
|
|
||||||
|
|
||||||
systemd.services.golem-provider = {
|
|
||||||
description = "Golem Provider";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
ExecStart = "${yagna}/bin/golemsp run --no-interactive ${accountFlag}";
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = "5";
|
|
||||||
User = "golem";
|
|
||||||
Group = "golem";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
let
|
|
||||||
inherit (lib) mkOption;
|
|
||||||
|
|
||||||
inherit (lib.types) nullOr str;
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.golem-provider = {
|
|
||||||
account = mkOption {
|
|
||||||
type = nullOr str;
|
|
||||||
description = ''
|
|
||||||
Ethereum address for payouts.
|
|
||||||
|
|
||||||
Leave empty to automatically generate a new address upon first start.
|
|
||||||
'';
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [ ../. ];
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A matrix bridge to communicate with IRC"
|
|
||||||
---
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"heisenbridge"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
services.heisenbridge = {
|
|
||||||
enable = true;
|
|
||||||
homeserver = "http://localhost:8008"; # TODO: Sync with matrix-synapse
|
|
||||||
};
|
|
||||||
services.matrix-synapse.settings.app_service_config_files = [
|
|
||||||
"/var/lib/heisenbridge/registration.yml"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Automatically provisions wifi credentials"
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! Warning
|
|
||||||
If you've been using network manager + wpa_supplicant and now are switching to IWD read this migration guide:
|
|
||||||
https://iwd.wiki.kernel.org/networkmanager#converting_network_profiles
|
|
@ -1,86 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.clan.iwd;
|
|
||||||
secret_path = ssid: config.clan.core.facts.services."iwd.${ssid}".secret."iwd.${ssid}".path;
|
|
||||||
secret_generator = name: value: {
|
|
||||||
name = "iwd.${value.ssid}";
|
|
||||||
value =
|
|
||||||
let
|
|
||||||
secret_name = "iwd.${value.ssid}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
secret.${secret_name} = { };
|
|
||||||
generator.prompt = "Wifi password for '${value.ssid}'";
|
|
||||||
generator.script = ''
|
|
||||||
config="
|
|
||||||
[Security]
|
|
||||||
Passphrase=$prompt_value
|
|
||||||
"
|
|
||||||
echo "$config" > $secrets/${secret_name}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.iwd = {
|
|
||||||
networks = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
ssid = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = name;
|
|
||||||
description = "The name of the wifi network";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default = { };
|
|
||||||
description = "Wifi networks to predefine";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"iwd"
|
|
||||||
"enable"
|
|
||||||
] "Just define clan.iwd.networks to enable it")
|
|
||||||
];
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
|
||||||
(lib.mkIf (cfg.networks != { }) {
|
|
||||||
# Systemd tmpfiles rule to create /var/lib/iwd/example.psk file
|
|
||||||
systemd.tmpfiles.rules = lib.mapAttrsToList (
|
|
||||||
_: value: "C /var/lib/iwd/${value.ssid}.psk 0600 root root - ${secret_path value.ssid}"
|
|
||||||
) cfg.networks;
|
|
||||||
|
|
||||||
clan.core.facts.services = lib.mapAttrs' secret_generator cfg.networks;
|
|
||||||
|
|
||||||
# TODO: restart the iwd.service if something changes
|
|
||||||
})
|
|
||||||
{
|
|
||||||
# disable wpa supplicant
|
|
||||||
networking.wireless.enable = false;
|
|
||||||
|
|
||||||
# Set the network manager backend to iwd
|
|
||||||
networking.networkmanager.wifi.backend = "iwd";
|
|
||||||
|
|
||||||
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
|
|
||||||
networking.wireless.iwd = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
Network = {
|
|
||||||
EnableIPv6 = true;
|
|
||||||
RoutePriorityOffset = 300;
|
|
||||||
};
|
|
||||||
Settings.AutoConnect = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
{ }
|
|
@ -1,3 +1,2 @@
|
|||||||
---
|
Automatically backups current machine to local directory.
|
||||||
description = "Automatically backups current machine to local directory."
|
|
||||||
---
|
---
|
||||||
|
@ -6,10 +6,7 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.localbackup;
|
cfg = config.clan.localbackup;
|
||||||
uniqueFolders = lib.unique (
|
rsnapshotConfig = target: states: ''
|
||||||
lib.flatten (lib.mapAttrsToList (_name: state: state.folders) config.clan.core.state)
|
|
||||||
);
|
|
||||||
rsnapshotConfig = target: ''
|
|
||||||
config_version 1.2
|
config_version 1.2
|
||||||
snapshot_root ${target.directory}
|
snapshot_root ${target.directory}
|
||||||
sync_first 1
|
sync_first 1
|
||||||
@ -20,6 +17,12 @@ let
|
|||||||
cmd_logger ${pkgs.inetutils}/bin/logger
|
cmd_logger ${pkgs.inetutils}/bin/logger
|
||||||
cmd_du ${pkgs.coreutils}/bin/du
|
cmd_du ${pkgs.coreutils}/bin/du
|
||||||
cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
|
cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
|
||||||
|
${lib.optionalString (target.preBackupHook != null) ''
|
||||||
|
cmd_preexec ${pkgs.writeShellScript "preexec.sh" ''
|
||||||
|
set -efu -o pipefail
|
||||||
|
${target.preBackupHook}
|
||||||
|
''}
|
||||||
|
''}
|
||||||
|
|
||||||
${lib.optionalString (target.postBackupHook != null) ''
|
${lib.optionalString (target.postBackupHook != null) ''
|
||||||
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
|
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
|
||||||
@ -28,9 +31,11 @@ let
|
|||||||
''}
|
''}
|
||||||
''}
|
''}
|
||||||
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
||||||
|
${lib.concatMapStringsSep "\n" (state: ''
|
||||||
${lib.concatMapStringsSep "\n" (folder: ''
|
${lib.concatMapStringsSep "\n" (folder: ''
|
||||||
backup ${folder} ${config.networking.hostName}/
|
backup ${folder} ${config.networking.hostName}/
|
||||||
'') uniqueFolders}
|
'') state.folders}
|
||||||
|
'') states}
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@ -124,29 +129,14 @@ in
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
${lib.concatMapStringsSep "\n" (target: ''
|
${lib.concatMapStringsSep "\n" (target: ''
|
||||||
|
(
|
||||||
${mountHook target}
|
${mountHook target}
|
||||||
echo "Creating backup '${target.name}'"
|
echo "Creating backup '${target.name}'"
|
||||||
|
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" sync
|
||||||
${lib.optionalString (target.preBackupHook != null) ''
|
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" snapshot
|
||||||
(
|
|
||||||
${target.preBackupHook}
|
|
||||||
)
|
)
|
||||||
''}
|
'') (builtins.attrValues cfg.targets)}
|
||||||
|
'')
|
||||||
declare -A preCommandErrors
|
|
||||||
${lib.concatMapStringsSep "\n" (
|
|
||||||
state:
|
|
||||||
lib.optionalString (state.preBackupCommand != null) ''
|
|
||||||
echo "Running pre-backup command for ${state.name}"
|
|
||||||
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
|
|
||||||
preCommandErrors["${state.name}"]=1
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
) (builtins.attrValues config.clan.core.state)}
|
|
||||||
|
|
||||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" sync
|
|
||||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" snapshot
|
|
||||||
'') (builtins.attrValues cfg.targets)}'')
|
|
||||||
(pkgs.writeShellScriptBin "localbackup-list" ''
|
(pkgs.writeShellScriptBin "localbackup-list" ''
|
||||||
set -efu -o pipefail
|
set -efu -o pipefail
|
||||||
export PATH=${
|
export PATH=${
|
||||||
@ -177,14 +167,6 @@ in
|
|||||||
pkgs.gawk
|
pkgs.gawk
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if [[ "''${NAME:-}" == "" ]]; then
|
|
||||||
echo "No backup name given via NAME environment variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ "''${FOLDERS:-}" == "" ]]; then
|
|
||||||
echo "No folders given via FOLDERS environment variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
||||||
backupname=''${NAME#$name::}
|
backupname=''${NAME#$name::}
|
||||||
|
|
||||||
@ -200,9 +182,8 @@ in
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
|
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
||||||
for folder in "''${FOLDER[@]}"; do
|
for folder in "''${FOLDER[@]}"; do
|
||||||
mkdir -p "$folder"
|
|
||||||
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
||||||
done
|
done
|
||||||
'')
|
'')
|
||||||
@ -232,7 +213,7 @@ in
|
|||||||
''
|
''
|
||||||
) cfg.targets;
|
) cfg.targets;
|
||||||
|
|
||||||
clan.core.backups.providers.localbackup = {
|
clanCore.backups.providers.localbackup = {
|
||||||
# TODO list needs to run locally or on the remote machine
|
# TODO list needs to run locally or on the remote machine
|
||||||
list = "localbackup-list";
|
list = "localbackup-list";
|
||||||
create = "localbackup-create";
|
create = "localbackup-create";
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
---
|
Securely sharing files and messages over a local network without internet connectivity.
|
||||||
description = "Securely sharing files and messages over a local network without internet connectivity."
|
|
||||||
---
|
---
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.clan.localsend.enable {
|
config = lib.mkIf config.clan.localsend.enable {
|
||||||
clan.core.state.localsend.folders = [
|
clanCore.state.localsend.folders = [
|
||||||
"/var/localsend"
|
"/var/localsend"
|
||||||
config.clan.localsend.defaultLocation
|
config.clan.localsend.defaultLocation
|
||||||
];
|
];
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
---
|
A federated messaging server with end-to-end encryption.
|
||||||
description = "A federated messaging server with end-to-end encryption."
|
|
||||||
---
|
---
|
||||||
|
@ -6,74 +6,20 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.matrix-synapse;
|
cfg = config.clan.matrix-synapse;
|
||||||
element-web =
|
|
||||||
pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; }
|
|
||||||
''
|
|
||||||
cp -r ${pkgs.element-web} $out
|
|
||||||
chmod -R u+w $out
|
|
||||||
jq '."default_server_config"."m.homeserver" = { "base_url": "https://${cfg.app_domain}:443", "server_name": "${cfg.server_tld}" }' \
|
|
||||||
> $out/config.json < ${pkgs.element-web}/config.json
|
|
||||||
ln -s $out/config.json $out/config.${cfg.app_domain}.json
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
# FIXME: This was taken from upstream. Drop this when our patch is upstream
|
|
||||||
{
|
{
|
||||||
options.services.matrix-synapse.package = lib.mkOption { readOnly = false; };
|
|
||||||
options.clan.matrix-synapse = {
|
options.clan.matrix-synapse = {
|
||||||
server_tld = lib.mkOption {
|
enable = lib.mkEnableOption "Enable matrix-synapse";
|
||||||
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The address that is suffixed after your username i.e @alice:example.com";
|
description = "The domain name of the matrix server";
|
||||||
example = "example.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
app_domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The matrix server hostname also serves the element client";
|
|
||||||
example = "matrix.example.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
users = lib.mkOption {
|
|
||||||
default = { };
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = name;
|
|
||||||
description = "The name of the user";
|
|
||||||
};
|
|
||||||
|
|
||||||
admin = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Whether the user should be an admin";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
config = lib.mkIf cfg.enable {
|
||||||
)
|
|
||||||
);
|
|
||||||
description = "A list of users. Not that only new users will be created and existing ones are not modified.";
|
|
||||||
example.alice = {
|
|
||||||
admin = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
imports = [
|
|
||||||
../postgresql
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"matrix-synapse"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
../nginx
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
services.matrix-synapse = {
|
services.matrix-synapse = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
server_name = cfg.server_tld;
|
server_name = cfg.domain;
|
||||||
database = {
|
database = {
|
||||||
args.user = "matrix-synapse";
|
args.user = "matrix-synapse";
|
||||||
args.database = "matrix-synapse";
|
args.database = "matrix-synapse";
|
||||||
@ -83,7 +29,6 @@ in
|
|||||||
"turn:turn.matrix.org?transport=udp"
|
"turn:turn.matrix.org?transport=udp"
|
||||||
"turn:turn.matrix.org?transport=tcp"
|
"turn:turn.matrix.org?transport=tcp"
|
||||||
];
|
];
|
||||||
registration_shared_secret_path = "/run/synapse-registration-shared-secret";
|
|
||||||
listeners = [
|
listeners = [
|
||||||
{
|
{
|
||||||
port = 8008;
|
port = 8008;
|
||||||
@ -104,83 +49,52 @@ in
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
extraConfigFiles = [ "/var/lib/matrix-synapse/registration_shared_secret.yaml" ];
|
||||||
};
|
};
|
||||||
|
systemd.services.matrix-synapse.serviceConfig.ExecStartPre = [
|
||||||
|
"+${pkgs.writeScript "copy_registration_shared_secret" ''
|
||||||
|
#!/bin/sh
|
||||||
|
cp ${config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path} /var/lib/matrix-synapse/registration_shared_secret.yaml
|
||||||
|
chown matrix-synapse:matrix-synapse /var/lib/matrix-synapse/registration_shared_secret.yaml
|
||||||
|
chmod 600 /var/lib/matrix-synapse/registration_shared_secret.yaml
|
||||||
|
''}"
|
||||||
|
];
|
||||||
|
|
||||||
systemd.tmpfiles.settings."01-matrix" = {
|
clanCore.facts.services."matrix-synapse" = {
|
||||||
"/run/synapse-registration-shared-secret" = {
|
|
||||||
C.argument =
|
|
||||||
config.clan.core.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path;
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "matrix-synapse";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.postgresql.users.matrix-synapse = { };
|
|
||||||
clan.postgresql.databases.matrix-synapse.create.options = {
|
|
||||||
TEMPLATE = "template0";
|
|
||||||
LC_COLLATE = "C";
|
|
||||||
LC_CTYPE = "C";
|
|
||||||
ENCODING = "UTF8";
|
|
||||||
OWNER = "matrix-synapse";
|
|
||||||
};
|
|
||||||
clan.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ];
|
|
||||||
|
|
||||||
clan.core.facts.services =
|
|
||||||
{
|
|
||||||
"matrix-synapse" = {
|
|
||||||
secret."synapse-registration_shared_secret" = { };
|
secret."synapse-registration_shared_secret" = { };
|
||||||
generator.path = with pkgs; [
|
generator.path = with pkgs; [
|
||||||
coreutils
|
coreutils
|
||||||
pwgen
|
pwgen
|
||||||
];
|
];
|
||||||
generator.script = ''
|
generator.script = ''
|
||||||
echo -n "$(pwgen -s 32 1)" > "$secrets"/synapse-registration_shared_secret
|
echo "registration_shared_secret: $(pwgen -s 32 1)" > "$secrets"/synapse-registration_shared_secret
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
|
||||||
// lib.mapAttrs' (
|
|
||||||
name: user:
|
|
||||||
lib.nameValuePair "matrix-password-${user.name}" {
|
|
||||||
secret."matrix-password-${user.name}" = { };
|
|
||||||
generator.path = with pkgs; [ xkcdpass ];
|
|
||||||
generator.script = ''
|
|
||||||
xkcdpass -n 4 -d - > "$secrets"/${lib.escapeShellArg "matrix-password-${user.name}"}
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
) cfg.users;
|
|
||||||
|
|
||||||
systemd.services.matrix-synapse =
|
services.postgresql.enable = true;
|
||||||
let
|
# we need to use both ensusureDatabases and initialScript, because the former runs everytime but with the wrong collation
|
||||||
usersScript =
|
services.postgresql = {
|
||||||
''
|
ensureDatabases = [ "matrix-synapse" ];
|
||||||
while ! ${pkgs.netcat}/bin/nc -z -v ::1 8008; do
|
ensureUsers = [
|
||||||
if ! kill -0 "$MAINPID"; then exit 1; fi
|
|
||||||
sleep 1;
|
|
||||||
done
|
|
||||||
''
|
|
||||||
+ lib.concatMapStringsSep "\n" (user: ''
|
|
||||||
# only create user if it doesn't exist
|
|
||||||
/run/current-system/sw/bin/matrix-synapse-register_new_matrix_user --exists-ok --password-file ${
|
|
||||||
config.clan.core.facts.services."matrix-password-${user.name}".secret."matrix-password-${user.name}".path
|
|
||||||
} --user "${user.name}" ${if user.admin then "--admin" else "--no-admin"}
|
|
||||||
'') (lib.attrValues cfg.users);
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
path = [ pkgs.curl ];
|
name = "matrix-synapse";
|
||||||
serviceConfig.ExecStartPost = [
|
ensureDBOwnership = true;
|
||||||
(''+${pkgs.writeShellScript "matrix-synapse-create-users" usersScript}'')
|
}
|
||||||
];
|
];
|
||||||
|
initialScript = pkgs.writeText "synapse-init.sql" ''
|
||||||
|
CREATE DATABASE "matrix-synapse"
|
||||||
|
TEMPLATE template0
|
||||||
|
LC_COLLATE = "C"
|
||||||
|
LC_CTYPE = "C";
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts = {
|
virtualHosts = {
|
||||||
"${cfg.server_tld}" = {
|
${cfg.domain} = {
|
||||||
locations."= /.well-known/matrix/server".extraConfig = ''
|
locations."= /.well-known/matrix/server".extraConfig = ''
|
||||||
add_header Content-Type application/json;
|
add_header Content-Type application/json;
|
||||||
return 200 '${builtins.toJSON { "m.server" = "${cfg.app_domain}:443"; }}';
|
return 200 '${builtins.toJSON { "m.server" = "matrix.${cfg.domain}:443"; }}';
|
||||||
'';
|
'';
|
||||||
locations."= /.well-known/matrix/client".extraConfig = ''
|
locations."= /.well-known/matrix/client".extraConfig = ''
|
||||||
add_header Content-Type application/json;
|
add_header Content-Type application/json;
|
||||||
@ -188,7 +102,7 @@ in
|
|||||||
return 200 '${
|
return 200 '${
|
||||||
builtins.toJSON {
|
builtins.toJSON {
|
||||||
"m.homeserver" = {
|
"m.homeserver" = {
|
||||||
"base_url" = "https://${cfg.app_domain}";
|
"base_url" = "https://matrix.${cfg.domain}";
|
||||||
};
|
};
|
||||||
"m.identity_server" = {
|
"m.identity_server" = {
|
||||||
"base_url" = "https://vector.im";
|
"base_url" = "https://vector.im";
|
||||||
@ -196,15 +110,16 @@ in
|
|||||||
}
|
}
|
||||||
}';
|
}';
|
||||||
'';
|
'';
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
};
|
};
|
||||||
"${cfg.app_domain}" = {
|
"matrix.${cfg.domain}" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
locations."/".root = element-web;
|
locations."/_matrix" = {
|
||||||
locations."/_matrix".proxyPass = "http://localhost:8008"; # TODO: We should make the port configurable
|
proxyPass = "http://localhost:8008";
|
||||||
locations."/_synapse".proxyPass = "http://localhost:8008";
|
};
|
||||||
|
locations."/test".extraConfig = ''
|
||||||
|
return 200 "Hello, world!";
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
---
|
A desktop streaming client optimized for remote gaming and synchronized movie viewing.
|
||||||
description = "A desktop streaming client optimized for remote gaming and synchronized movie viewing."
|
|
||||||
---
|
---
|
||||||
|
@ -13,10 +13,10 @@ in
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/var/lib/moonlight' 0770 'user' 'users' - -"
|
"d '/var/lib/moonlight' 0770 'user' 'users' - -"
|
||||||
"C '/var/lib/moonlight/moonlight.cert' 0644 'user' 'users' - ${
|
"C '/var/lib/moonlight/moonlight.cert' 0644 'user' 'users' - ${
|
||||||
config.clan.core.facts.services.moonlight.secret."moonlight.cert".path or ""
|
config.clanCore.facts.services.moonlight.secret."moonlight.cert".path or ""
|
||||||
}"
|
}"
|
||||||
"C '/var/lib/moonlight/moonlight.key' 0644 'user' 'users' - ${
|
"C '/var/lib/moonlight/moonlight.key' 0644 'user' 'users' - ${
|
||||||
config.clan.core.facts.services.moonlight.secret."moonlight.key".path or ""
|
config.clanCore.facts.services.moonlight.secret."moonlight.key".path or ""
|
||||||
}"
|
}"
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ in
|
|||||||
systemd.user.services.moonlight-join = {
|
systemd.user.services.moonlight-join = {
|
||||||
description = "Join sunshine hosts";
|
description = "Join sunshine hosts";
|
||||||
script = ''${ms-accept}/bin/moonlight-sunshine-accept moonlight join --port ${builtins.toString defaultPort} --cert '${
|
script = ''${ms-accept}/bin/moonlight-sunshine-accept moonlight join --port ${builtins.toString defaultPort} --cert '${
|
||||||
config.clan.core.facts.services.moonlight.public."moonlight.cert".value or ""
|
config.clanCore.facts.services.moonlight.public."moonlight.cert".value or ""
|
||||||
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
|
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
@ -68,7 +68,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clan.core.facts.services.moonlight = {
|
clanCore.facts.services.moonlight = {
|
||||||
secret."moonlight.key" = { };
|
secret."moonlight.key" = { };
|
||||||
secret."moonlight.cert" = { };
|
secret."moonlight.cert" = { };
|
||||||
public."moonlight.cert" = { };
|
public."moonlight.cert" = { };
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Open Source, Low Latency, High Quality Voice Chat."
|
|
||||||
categories = ["chat", "voice"]
|
|
||||||
---
|
|
||||||
The mumble clan module gives you:
|
|
||||||
|
|
||||||
- True low latency voice communication.
|
|
||||||
- Secure, authenticated encryption.
|
|
||||||
- Free software.
|
|
||||||
- Backed by a large and active open-source community.
|
|
||||||
|
|
||||||
This all set up in a way that allows peer-to-peer hosting.
|
|
||||||
Every machine inside the clan can be a host for mumble,
|
|
||||||
and thus it doesn't matter who in the network is online - as long as two people are online they are able to chat with each other.
|
|
@ -1,104 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
clanDir = config.clan.core.clanDir;
|
|
||||||
machineDir = clanDir + "/machines/";
|
|
||||||
machinesFileSet = builtins.readDir machineDir;
|
|
||||||
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
|
||||||
machineJson = builtins.toJSON machines;
|
|
||||||
certificateMachinePath = machines: machineDir + "/${machines}" + "/facts/mumble-cert";
|
|
||||||
certificatesUnchecked = builtins.map (
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
fullPath = certificateMachinePath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then machine else null
|
|
||||||
) machines;
|
|
||||||
certificate = lib.filter (machine: machine != null) certificatesUnchecked;
|
|
||||||
machineCert = builtins.map (
|
|
||||||
machine: (lib.nameValuePair machine (builtins.readFile (certificateMachinePath machine)))
|
|
||||||
) certificate;
|
|
||||||
machineCertJson = builtins.toJSON machineCert;
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.services.mumble = {
|
|
||||||
user = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "alice";
|
|
||||||
description = "The user mumble should be set up for.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
services.murmur = {
|
|
||||||
enable = true;
|
|
||||||
logDays = -1;
|
|
||||||
registerName = config.clan.core.machineName;
|
|
||||||
openFirewall = true;
|
|
||||||
bonjour = true;
|
|
||||||
sslKey = config.clan.core.facts.services.mumble.secret.mumble-key.path;
|
|
||||||
sslCert = config.clan.core.facts.services.mumble.public.mumble-cert.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.state.mumble.folders = [
|
|
||||||
"/var/lib/mumble"
|
|
||||||
"/var/lib/murmur"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d '/var/lib/mumble' 0770 '${config.clan.services.mumble.user}' 'users' - -"
|
|
||||||
];
|
|
||||||
|
|
||||||
environment.systemPackages =
|
|
||||||
let
|
|
||||||
mumbleCfgDir = "/var/lib/mumble";
|
|
||||||
mumbleDatabasePath = "${mumbleCfgDir}/mumble.sqlite";
|
|
||||||
mumbleCfgPath = "/var/lib/mumble/mumble_settings.json";
|
|
||||||
populate-channels = pkgs.writers.writePython3 "mumble-populate-channels" {
|
|
||||||
libraries = [
|
|
||||||
pkgs.python3Packages.cryptography
|
|
||||||
pkgs.python3Packages.pyopenssl
|
|
||||||
];
|
|
||||||
flakeIgnore = [
|
|
||||||
# We don't live in the dark ages anymore.
|
|
||||||
# Languages like Python that are whitespace heavy will overrun
|
|
||||||
# 79 characters..
|
|
||||||
"E501"
|
|
||||||
];
|
|
||||||
} (builtins.readFile ./mumble-populate-channels.py);
|
|
||||||
mumble = pkgs.writeShellScriptBin "mumble" ''
|
|
||||||
set -xeu
|
|
||||||
mkdir -p ${mumbleCfgDir}
|
|
||||||
pushd "${mumbleCfgDir}"
|
|
||||||
XDG_DATA_HOME=${mumbleCfgDir}
|
|
||||||
XDG_DATA_DIR=${mumbleCfgDir}
|
|
||||||
${populate-channels} --ensure-config '${mumbleCfgPath}' --db-location ${mumbleDatabasePath}
|
|
||||||
echo ${machineCertJson}
|
|
||||||
${populate-channels} --machines '${machineJson}' --username ${config.clan.core.machineName} --db-location ${mumbleDatabasePath}
|
|
||||||
${populate-channels} --servers '${machineCertJson}' --username ${config.clan.core.machineName} --db-location ${mumbleDatabasePath} --cert True
|
|
||||||
${pkgs.mumble}/bin/mumble --config ${mumbleCfgPath} "$@"
|
|
||||||
popd
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
[ mumble ];
|
|
||||||
|
|
||||||
clan.core.facts.services.mumble = {
|
|
||||||
secret.mumble-key = { };
|
|
||||||
public.mumble-cert = { };
|
|
||||||
generator.path = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssl
|
|
||||||
];
|
|
||||||
generator.script = ''
|
|
||||||
openssl genrsa -out $secrets/mumble-key 2048
|
|
||||||
openssl req -new -x509 -key $secrets/mumble-key -out $facts/mumble-cert
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,247 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import sqlite3
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_config(path: Path, db_path: Path) -> None:
|
|
||||||
# Default JSON structure if the file doesn't exist
|
|
||||||
default_json = {
|
|
||||||
"misc": {
|
|
||||||
"audio_wizard_has_been_shown": True,
|
|
||||||
"database_location": str(db_path),
|
|
||||||
"viewed_server_ping_consent_message": True,
|
|
||||||
},
|
|
||||||
"settings_version": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the file exists
|
|
||||||
if path.exists():
|
|
||||||
data = json.loads(path.read_text())
|
|
||||||
else:
|
|
||||||
data = default_json
|
|
||||||
# Create the file with default JSON structure
|
|
||||||
with path.open("w") as file:
|
|
||||||
json.dump(data, file, indent=4)
|
|
||||||
|
|
||||||
# TODO: make sure to only update the diff
|
|
||||||
updated_data = {**default_json, **data}
|
|
||||||
|
|
||||||
# Write the modified JSON object back to the file
|
|
||||||
with path.open("w") as file:
|
|
||||||
json.dump(updated_data, file, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_database(db_location: str) -> None:
|
|
||||||
"""
|
|
||||||
Initializes the database. If the database or the servers table does not exist, it creates them.
|
|
||||||
|
|
||||||
:param db_location: The path to the SQLite database
|
|
||||||
"""
|
|
||||||
conn = sqlite3.connect(db_location)
|
|
||||||
try:
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Create the servers table if it doesn't exist
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS servers (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
hostname TEXT NOT NULL,
|
|
||||||
port INTEGER NOT NULL,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL,
|
|
||||||
url TEXT
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Commit the changes
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"An error occurred while initializing the database: {e}")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_certificates(
|
|
||||||
db_location: str, hostname: str, port: str, digest: str
|
|
||||||
) -> None:
|
|
||||||
# Connect to the SQLite database
|
|
||||||
conn = sqlite3.connect(db_location)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create a cursor object
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# TODO: check if cert already there
|
|
||||||
# if server_check(cursor, name, hostname):
|
|
||||||
# print(
|
|
||||||
# f"Server with name '{name}' and hostname '{hostname}' already exists."
|
|
||||||
# )
|
|
||||||
# return
|
|
||||||
|
|
||||||
# SQL command to insert data into the servers table
|
|
||||||
insert_query = """
|
|
||||||
INSERT INTO cert (hostname, port, digest)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Data to be inserted
|
|
||||||
data = (hostname, port, digest)
|
|
||||||
|
|
||||||
# Execute the insert command with the provided data
|
|
||||||
cursor.execute(insert_query, data)
|
|
||||||
|
|
||||||
# Commit the changes
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print("Data has been successfully inserted.")
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"An error occurred: {e}")
|
|
||||||
finally:
|
|
||||||
# Close the connection
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_digest(cert: str) -> str:
|
|
||||||
from cryptography import x509
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
|
|
||||||
cert = cert.strip()
|
|
||||||
cert = cert.encode("utf-8")
|
|
||||||
cert = x509.load_pem_x509_certificate(cert, default_backend())
|
|
||||||
digest = cert.fingerprint(hashes.SHA1()).hex()
|
|
||||||
return digest
|
|
||||||
|
|
||||||
|
|
||||||
def server_check(cursor: str, name: str, hostname: str) -> bool:
|
|
||||||
"""
|
|
||||||
Check if a server with the given name and hostname already exists.
|
|
||||||
|
|
||||||
:param cursor: The database cursor
|
|
||||||
:param name: The name of the server
|
|
||||||
:param hostname: The hostname of the server
|
|
||||||
:return: True if the server exists, False otherwise
|
|
||||||
"""
|
|
||||||
check_query = """
|
|
||||||
SELECT 1 FROM servers WHERE name = ? AND hostname = ?
|
|
||||||
"""
|
|
||||||
cursor.execute(check_query, (name, hostname))
|
|
||||||
return cursor.fetchone() is not None
|
|
||||||
|
|
||||||
|
|
||||||
def insert_server(
|
|
||||||
name: str,
|
|
||||||
hostname: str,
|
|
||||||
port: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
url: str,
|
|
||||||
db_location: str,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Inserts a new server record into the servers table.
|
|
||||||
|
|
||||||
:param name: The name of the server
|
|
||||||
:param hostname: The hostname of the server
|
|
||||||
:param port: The port number
|
|
||||||
:param username: The username
|
|
||||||
:param password: The password
|
|
||||||
:param url: The URL
|
|
||||||
"""
|
|
||||||
# Connect to the SQLite database
|
|
||||||
conn = sqlite3.connect(db_location)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create a cursor object
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
if server_check(cursor, name, hostname):
|
|
||||||
print(
|
|
||||||
f"Server with name '{name}' and hostname '{hostname}' already exists."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# SQL command to insert data into the servers table
|
|
||||||
insert_query = """
|
|
||||||
INSERT INTO servers (name, hostname, port, username, password, url)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Data to be inserted
|
|
||||||
data = (name, hostname, port, username, password, url)
|
|
||||||
|
|
||||||
# Execute the insert command with the provided data
|
|
||||||
cursor.execute(insert_query, data)
|
|
||||||
|
|
||||||
# Commit the changes
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print("Data has been successfully inserted.")
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"An error occurred: {e}")
|
|
||||||
finally:
|
|
||||||
# Close the connection
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
port = 64738
|
|
||||||
password = ""
|
|
||||||
url = None
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog="initialize_mumble",
|
|
||||||
)
|
|
||||||
|
|
||||||
subparser = parser.add_subparsers(dest="certificates")
|
|
||||||
# cert_parser = subparser.add_parser("certificates")
|
|
||||||
|
|
||||||
parser.add_argument("--cert")
|
|
||||||
parser.add_argument("--digest")
|
|
||||||
parser.add_argument("--machines")
|
|
||||||
parser.add_argument("--servers")
|
|
||||||
parser.add_argument("--username")
|
|
||||||
parser.add_argument("--db-location")
|
|
||||||
parser.add_argument("--ensure-config", type=Path)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
print(args)
|
|
||||||
|
|
||||||
if args.ensure_config:
|
|
||||||
ensure_config(args.ensure_config, args.db_location)
|
|
||||||
print("Initialized config")
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
if args.servers:
|
|
||||||
print(args.servers)
|
|
||||||
servers = json.loads(f"{args.servers}")
|
|
||||||
db_location = args.db_location
|
|
||||||
for server in servers:
|
|
||||||
digest = calculate_digest(server.get("value"))
|
|
||||||
name = server.get("name")
|
|
||||||
initialize_certificates(db_location, name, port, digest)
|
|
||||||
print("Initialized certificates")
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
initialize_database(args.db_location)
|
|
||||||
|
|
||||||
# Insert the server into the database
|
|
||||||
print(args.machines)
|
|
||||||
machines = json.loads(f"{args.machines}")
|
|
||||||
print(machines)
|
|
||||||
print(list(machines))
|
|
||||||
|
|
||||||
for machine in list(machines):
|
|
||||||
print(f"Inserting {machine}.")
|
|
||||||
insert_server(
|
|
||||||
machine,
|
|
||||||
machine,
|
|
||||||
port,
|
|
||||||
args.username,
|
|
||||||
password,
|
|
||||||
url,
|
|
||||||
args.db_location,
|
|
||||||
)
|
|
@ -1,42 +0,0 @@
|
|||||||
{ pkgs, self, ... }:
|
|
||||||
pkgs.nixosTest {
|
|
||||||
name = "mumble";
|
|
||||||
nodes.peer1 =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.nixosModules.mumble
|
|
||||||
self.inputs.clan-core.nixosModules.clanCore
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
clan.core.machineName = "peer1";
|
|
||||||
clan.core.clanDir = ./.;
|
|
||||||
|
|
||||||
documentation.enable = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
nodes.peer2 =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.nixosModules.mumble
|
|
||||||
self.inputs.clan-core.nixosModules.clanCore
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
|
|
||||||
clan.core.machineName = "peer2";
|
|
||||||
clan.core.clanDir = ./.;
|
|
||||||
|
|
||||||
documentation.enable = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
'';
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Good defaults for the nginx webserver"
|
|
||||||
---
|
|
@ -1,69 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"nginx"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
|
|
||||||
];
|
|
||||||
options = {
|
|
||||||
clan.nginx.acme.email = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = ''
|
|
||||||
Email address for account creation and correspondence from the CA.
|
|
||||||
It is recommended to use the same email for all certs to avoid account
|
|
||||||
creation limits.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
security.acme.acceptTerms = true;
|
|
||||||
security.acme.defaults.email = config.clan.nginx.acme.email;
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
443
|
|
||||||
80
|
|
||||||
];
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
statusPage = lib.mkDefault true;
|
|
||||||
recommendedBrotliSettings = lib.mkDefault true;
|
|
||||||
recommendedGzipSettings = lib.mkDefault true;
|
|
||||||
recommendedOptimisation = lib.mkDefault true;
|
|
||||||
recommendedProxySettings = lib.mkDefault true;
|
|
||||||
recommendedTlsSettings = lib.mkDefault true;
|
|
||||||
recommendedZstdSettings = lib.mkDefault true;
|
|
||||||
|
|
||||||
# Nginx sends all the access logs to /var/log/nginx/access.log by default.
|
|
||||||
# instead of going to the journal!
|
|
||||||
commonHttpConfig = "access_log syslog:server=unix:/dev/log;";
|
|
||||||
|
|
||||||
resolver.addresses =
|
|
||||||
let
|
|
||||||
isIPv6 = addr: builtins.match ".*:.*:.*" addr != null;
|
|
||||||
escapeIPv6 = addr: if isIPv6 addr then "[${addr}]" else addr;
|
|
||||||
cloudflare = [
|
|
||||||
"1.1.1.1"
|
|
||||||
"2606:4700:4700::1111"
|
|
||||||
];
|
|
||||||
resolvers =
|
|
||||||
if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers;
|
|
||||||
in
|
|
||||||
map escapeIPv6 resolvers;
|
|
||||||
|
|
||||||
sslDhparam = config.security.dhparams.params.nginx.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
security.dhparams = {
|
|
||||||
enable = true;
|
|
||||||
params.nginx = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Define package sets from nixpkgs and install them on one or more machines"
|
|
||||||
categories = ["packages"]
|
|
||||||
---
|
|
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
options.clan.packages = {
|
|
||||||
packages = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
description = "The packages to install on the machine";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
environment.systemPackages = map (
|
|
||||||
pName: lib.getAttrFromPath (lib.splitString "." pName) pkgs
|
|
||||||
) config.clan.packages.packages;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
{ }
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A free and open-source relational database management system (RDBMS) emphasizing extensibility and SQL compliance."
|
|
||||||
---
|
|
@ -1,226 +0,0 @@
|
|||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
createDatatbaseState =
|
|
||||||
db:
|
|
||||||
let
|
|
||||||
folder = "/var/backup/postgres/${db.name}";
|
|
||||||
current = "${folder}/pg-dump";
|
|
||||||
compression = lib.optionalString (lib.versionAtLeast config.services.postgresql.package.version "16") "--compress=zstd";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
folders = [ folder ];
|
|
||||||
preBackupScript = ''
|
|
||||||
export PATH=${
|
|
||||||
lib.makeBinPath [
|
|
||||||
config.services.postgresql.package
|
|
||||||
config.systemd.package
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.util-linux
|
|
||||||
pkgs.zstd
|
|
||||||
]
|
|
||||||
}
|
|
||||||
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir -p "${folder}"
|
|
||||||
runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp"
|
|
||||||
mv "${current}.tmp" ${current}
|
|
||||||
'';
|
|
||||||
postRestoreScript = ''
|
|
||||||
export PATH=${
|
|
||||||
lib.makeBinPath [
|
|
||||||
config.services.postgresql.package
|
|
||||||
config.systemd.package
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.util-linux
|
|
||||||
pkgs.zstd
|
|
||||||
pkgs.gnugrep
|
|
||||||
]
|
|
||||||
}
|
|
||||||
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo "Waiting for postgres to be ready..."
|
|
||||||
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
|
|
||||||
if ! systemctl is-active postgresql; then exit 1; fi
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -e "${current}" ]]; then
|
|
||||||
(
|
|
||||||
systemctl stop ${lib.concatStringsSep " " db.restore.stopOnRestore}
|
|
||||||
trap "systemctl start ${lib.concatStringsSep " " db.restore.stopOnRestore}" EXIT
|
|
||||||
|
|
||||||
mkdir -p "${folder}"
|
|
||||||
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
|
|
||||||
runuser -u postgres -- dropdb "${db.name}"
|
|
||||||
fi
|
|
||||||
runuser -u postgres -- pg_restore -C -d postgres "${current}"
|
|
||||||
)
|
|
||||||
else
|
|
||||||
echo No database backup found, skipping restore
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
createDatabase = db: ''
|
|
||||||
CREATE DATABASE "${db.name}" ${
|
|
||||||
lib.concatStringsSep " " (
|
|
||||||
lib.mapAttrsToList (name: value: "${name} = '${value}'") db.create.options
|
|
||||||
)
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
cfg = config.clan.postgresql;
|
|
||||||
|
|
||||||
userClauses = lib.mapAttrsToList (
|
|
||||||
_: user:
|
|
||||||
''$PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' ''
|
|
||||||
) cfg.users;
|
|
||||||
databaseClauses = lib.mapAttrsToList (
|
|
||||||
name: db:
|
|
||||||
lib.optionalString db.create.enable ''$PSQL -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${name}'" | grep -q 1 || $PSQL -d postgres -c ${lib.escapeShellArg (createDatabase db)} ''
|
|
||||||
) cfg.databases;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.postgresql = {
|
|
||||||
# we are reimplemeting ensureDatabase and ensureUser options here to allow to create databases with options
|
|
||||||
databases = lib.mkOption {
|
|
||||||
description = "Databases to create";
|
|
||||||
default = { };
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = name;
|
|
||||||
description = "Database name.";
|
|
||||||
};
|
|
||||||
service = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = name;
|
|
||||||
description = "Service name that we associate with the database.";
|
|
||||||
};
|
|
||||||
# set to false, in case the upstream module uses ensureDatabase option
|
|
||||||
create.enable = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Create the database if it does not exist.";
|
|
||||||
};
|
|
||||||
create.options = lib.mkOption {
|
|
||||||
description = "Options to pass to the CREATE DATABASE command.";
|
|
||||||
type = lib.types.lazyAttrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
example = {
|
|
||||||
TEMPLATE = "template0";
|
|
||||||
LC_COLLATE = "C";
|
|
||||||
LC_CTYPE = "C";
|
|
||||||
ENCODING = "UTF8";
|
|
||||||
OWNER = "foo";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
restore.stopOnRestore = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = "List of systemd services to stop before restoring the database.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
users = lib.mkOption {
|
|
||||||
description = "Users to create";
|
|
||||||
default = { };
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options.name = lib.mkOption {
|
|
||||||
description = "User name";
|
|
||||||
type = lib.types.str;
|
|
||||||
default = name;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
services.postgresql.settings = {
|
|
||||||
wal_level = "replica";
|
|
||||||
max_wal_senders = 3;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.postgresql.enable = true;
|
|
||||||
# We are duplicating a bit the upstream module but allow to create databases with options
|
|
||||||
systemd.services.postgresql.postStart = ''
|
|
||||||
PSQL="psql --port=${builtins.toString config.services.postgresql.settings.port}"
|
|
||||||
|
|
||||||
while ! $PSQL -d postgres -c "" 2> /dev/null; do
|
|
||||||
if ! kill -0 "$MAINPID"; then exit 1; fi
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
${lib.concatStringsSep "\n" userClauses}
|
|
||||||
${lib.concatStringsSep "\n" databaseClauses}
|
|
||||||
'';
|
|
||||||
|
|
||||||
clan.core.state = lib.mapAttrs' (
|
|
||||||
_: db: lib.nameValuePair db.service (createDatatbaseState db)
|
|
||||||
) config.clan.postgresql.databases;
|
|
||||||
|
|
||||||
environment.systemPackages = builtins.map (
|
|
||||||
db:
|
|
||||||
let
|
|
||||||
folder = "/var/backup/postgres/${db.name}";
|
|
||||||
current = "${folder}/pg-dump";
|
|
||||||
in
|
|
||||||
pkgs.writeShellScriptBin "postgres-db-restore-command-${db.name}" ''
|
|
||||||
export PATH=${
|
|
||||||
lib.makeBinPath [
|
|
||||||
config.services.postgresql.package
|
|
||||||
config.systemd.package
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.util-linux
|
|
||||||
pkgs.zstd
|
|
||||||
pkgs.gnugrep
|
|
||||||
]
|
|
||||||
}
|
|
||||||
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo "Waiting for postgres to be ready..."
|
|
||||||
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
|
|
||||||
if ! systemctl is-active postgresql; then exit 1; fi
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -e "${current}" ]]; then
|
|
||||||
(
|
|
||||||
${
|
|
||||||
lib.optionalString (db.restore.stopOnRestore != [ ]) ''
|
|
||||||
systemctl stop ${builtins.toString db.restore.stopOnRestore}
|
|
||||||
trap "systemctl start ${builtins.toString db.restore.stopOnRestore}" EXIT
|
|
||||||
''
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdir -p "${folder}"
|
|
||||||
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
|
|
||||||
runuser -u postgres -- dropdb "${db.name}"
|
|
||||||
fi
|
|
||||||
runuser -u postgres -- pg_restore -C -d postgres "${current}"
|
|
||||||
)
|
|
||||||
else
|
|
||||||
echo No database backup found, skipping restore
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
) (builtins.attrValues config.clan.postgresql.databases);
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
---
|
Automatically generates and configures a password for the root user.
|
||||||
description = "Automatically generates and configures a password for the root user."
|
|
||||||
---
|
---
|
||||||
|
|
||||||
After the system was installed/deployed the following command can be used to display the root-password:
|
After the system was installed/deployed the following command can be used to display the root-password:
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
{
|
{ pkgs, config, ... }:
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
users.mutableUsers = false;
|
users.mutableUsers = false;
|
||||||
users.users.root.hashedPasswordFile =
|
users.users.root.hashedPasswordFile =
|
||||||
config.clan.core.facts.services.root-password.secret.password-hash.path;
|
config.clanCore.facts.services.root-password.secret.password-hash.path;
|
||||||
|
sops.secrets."${config.clanCore.machineName}-password-hash".neededForUsers = true;
|
||||||
sops.secrets = lib.mkIf (config.clan.core.facts.secretStore == "sops") {
|
clanCore.facts.services.root-password = {
|
||||||
"${config.clan.core.machineName}-password-hash".neededForUsers = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.facts.services.root-password = {
|
|
||||||
secret.password = { };
|
secret.password = { };
|
||||||
secret.password-hash = { };
|
secret.password-hash = { };
|
||||||
generator.path = with pkgs; [
|
generator.path = with pkgs; [
|
||||||
@ -22,8 +13,8 @@
|
|||||||
mkpasswd
|
mkpasswd
|
||||||
];
|
];
|
||||||
generator.script = ''
|
generator.script = ''
|
||||||
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > $secrets/password
|
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/password
|
||||||
cat $secrets/password | mkpasswd -s -m sha-512 | tr -d "\n" > $secrets/password-hash
|
cat $secrets/password | mkpasswd -s -m sha-512 > $secrets/password-hash
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Configures partitioning of the main disk"
|
|
||||||
categories = ["disk-layout"]
|
|
||||||
---
|
|
||||||
# Primary Disk Layout
|
|
||||||
|
|
||||||
A module for the "disk-layout" category MUST be choosen.
|
|
||||||
|
|
||||||
There is exactly one slot for this type of module in the UI, if you don't fill the slot, your machine cannot boot
|
|
||||||
|
|
||||||
This module is a good choice for most machines. In the future clan will offer a broader choice of disk-layouts
|
|
||||||
|
|
||||||
The UI will ask for the options of this module:
|
|
||||||
|
|
||||||
`device: "/dev/null"`
|
|
||||||
|
|
||||||
# Usage example
|
|
||||||
|
|
||||||
`inventory.json`
|
|
||||||
```json
|
|
||||||
"services": {
|
|
||||||
"single-disk": {
|
|
||||||
"default": {
|
|
||||||
"meta": {
|
|
||||||
"name": "single-disk"
|
|
||||||
},
|
|
||||||
"roles": {
|
|
||||||
"default": {
|
|
||||||
"machines": ["jon"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"machines": {
|
|
||||||
"jon": {
|
|
||||||
"config": {
|
|
||||||
"device": "/dev/null"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -1 +0,0 @@
|
|||||||
{ }
|
|
@ -1,3 +1,2 @@
|
|||||||
---
|
Enables secure remote access to the machine over ssh
|
||||||
description = "Enables secure remote access to the machine over ssh"
|
|
||||||
---
|
---
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
services.openssh.hostKeys = [
|
services.openssh.hostKeys = [
|
||||||
{
|
{
|
||||||
path = config.clan.core.facts.services.openssh.secret."ssh.id_ed25519".path;
|
path = config.clanCore.facts.services.openssh.secret."ssh.id_ed25519".path;
|
||||||
type = "ed25519";
|
type = "ed25519";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
clan.core.facts.services.openssh = {
|
clanCore.facts.services.openssh = {
|
||||||
secret."ssh.id_ed25519" = { };
|
secret."ssh.id_ed25519" = { };
|
||||||
public."ssh.id_ed25519.pub" = { };
|
public."ssh.id_ed25519.pub" = { };
|
||||||
generator.path = [
|
generator.path = [
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
{
|
|
||||||
system.stateVersion = config.clan.core.vars.generators.state-version.files.version.value;
|
|
||||||
|
|
||||||
clan.core.vars.generators.state-version = {
|
|
||||||
files.version.secret = false;
|
|
||||||
runtimeInputs = [ ];
|
|
||||||
script = ''
|
|
||||||
echo ${lib.versions.majorMinor lib.version} > $out/version
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,3 +1,2 @@
|
|||||||
---
|
Statically configure the host names of machines based on their respective zerotier-ip.
|
||||||
description = "Statically configure the host names of machines based on their respective zerotier-ip."
|
|
||||||
---
|
---
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
excludeHosts = lib.mkOption {
|
excludeHosts = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default =
|
default =
|
||||||
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clan.core.machineName ];
|
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clanCore.machineName ];
|
||||||
description = "Hosts that should be excluded";
|
description = "Hosts that should be excluded";
|
||||||
};
|
};
|
||||||
topLevelDomain = lib.mkOption {
|
topLevelDomain = lib.mkOption {
|
||||||
@ -16,23 +16,13 @@
|
|||||||
|
|
||||||
config.networking.hosts =
|
config.networking.hosts =
|
||||||
let
|
let
|
||||||
clanDir = config.clan.core.clanDir;
|
clanDir = config.clanCore.clanDir;
|
||||||
machineDir = clanDir + "/machines/";
|
machineDir = clanDir + "/machines/";
|
||||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||||
machinesFileSet = builtins.readDir machineDir;
|
machines = builtins.readDir machineDir;
|
||||||
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
|
||||||
networkIpsUnchecked = builtins.map (
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
fullPath = zerotierIpMachinePath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then machine else null
|
|
||||||
) machines;
|
|
||||||
networkIps = lib.filter (machine: machine != null) networkIpsUnchecked;
|
|
||||||
machinesWithIp = lib.filterAttrs (name: _: (lib.elem name networkIps)) machinesFileSet;
|
|
||||||
filteredMachines = lib.filterAttrs (
|
filteredMachines = lib.filterAttrs (
|
||||||
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
||||||
) machinesWithIp;
|
) machines;
|
||||||
in
|
in
|
||||||
lib.filterAttrs (_: value: value != null) (
|
lib.filterAttrs (_: value: value != null) (
|
||||||
lib.mapAttrs' (
|
lib.mapAttrs' (
|
||||||
@ -48,7 +38,7 @@
|
|||||||
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
|
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
{ }
|
null
|
||||||
) filteredMachines
|
) filteredMachines
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
---
|
A desktop streaming server optimized for remote gaming and synchronized movie viewing.
|
||||||
description = "A desktop streaming server optimized for remote gaming and synchronized movie viewing."
|
|
||||||
---
|
---
|
||||||
|
@ -97,10 +97,10 @@ in
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/var/lib/sunshine' 0770 'user' 'users' - -"
|
"d '/var/lib/sunshine' 0770 'user' 'users' - -"
|
||||||
"C '/var/lib/sunshine/sunshine.cert' 0644 'user' 'users' - ${
|
"C '/var/lib/sunshine/sunshine.cert' 0644 'user' 'users' - ${
|
||||||
config.clan.core.facts.services.sunshine.secret."sunshine.cert".path or ""
|
config.clanCore.facts.services.sunshine.secret."sunshine.cert".path or ""
|
||||||
}"
|
}"
|
||||||
"C '/var/lib/sunshine/sunshine.key' 0644 'user' 'users' - ${
|
"C '/var/lib/sunshine/sunshine.key' 0644 'user' 'users' - ${
|
||||||
config.clan.core.facts.services.sunshine.secret."sunshine.key".path or ""
|
config.clanCore.facts.services.sunshine.secret."sunshine.key".path or ""
|
||||||
}"
|
}"
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -117,8 +117,8 @@ in
|
|||||||
RestartSec = "5s";
|
RestartSec = "5s";
|
||||||
ReadWritePaths = [ "/var/lib/sunshine" ];
|
ReadWritePaths = [ "/var/lib/sunshine" ];
|
||||||
ReadOnlyPaths = [
|
ReadOnlyPaths = [
|
||||||
(config.clan.core.facts.services.sunshine.secret."sunshine.key".path or "")
|
(config.clanCore.facts.services.sunshine.secret."sunshine.key".path or "")
|
||||||
(config.clan.core.facts.services.sunshine.secret."sunshine.cert".path or "")
|
(config.clanCore.facts.services.sunshine.secret."sunshine.cert".path or "")
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
@ -137,7 +137,7 @@ in
|
|||||||
startLimitIntervalSec = 500;
|
startLimitIntervalSec = 500;
|
||||||
script = ''
|
script = ''
|
||||||
${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${
|
${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${
|
||||||
config.clan.core.facts.services.sunshine.public.sunshine-uuid.value or null
|
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
|
||||||
} --state-file /var/lib/sunshine/state.json
|
} --state-file /var/lib/sunshine/state.json
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@ -173,9 +173,9 @@ in
|
|||||||
startLimitIntervalSec = 500;
|
startLimitIntervalSec = 500;
|
||||||
script = ''
|
script = ''
|
||||||
${ms-accept}/bin/moonlight-sunshine-accept sunshine listen --port ${builtins.toString listenPort} --uuid ${
|
${ms-accept}/bin/moonlight-sunshine-accept sunshine listen --port ${builtins.toString listenPort} --uuid ${
|
||||||
config.clan.core.facts.services.sunshine.public.sunshine-uuid.value or null
|
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
|
||||||
} --state /var/lib/sunshine/state.json --cert '${
|
} --state /var/lib/sunshine/state.json --cert '${
|
||||||
config.clan.core.facts.services.sunshine.public."sunshine.cert".value or null
|
config.clanCore.facts.services.sunshine.public."sunshine.cert".value or null
|
||||||
}'
|
}'
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@ -187,7 +187,7 @@ in
|
|||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
clan.core.facts.services.ergochat = {
|
clanCore.facts.services.ergochat = {
|
||||||
secret."sunshine.key" = { };
|
secret."sunshine.key" = { };
|
||||||
secret."sunshine.cert" = { };
|
secret."sunshine.cert" = { };
|
||||||
public."sunshine-uuid" = { };
|
public."sunshine-uuid" = { };
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Statically configure syncthing peers through clan"
|
|
||||||
---
|
|
@ -1,108 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
clanDir = config.clan.core.clanDir;
|
|
||||||
machineDir = clanDir + "/machines/";
|
|
||||||
syncthingPublicKeyPath = machines: machineDir + machines + "/facts/syncthing.pub";
|
|
||||||
machinesFileSet = builtins.readDir machineDir;
|
|
||||||
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
|
||||||
syncthingPublicKeysUnchecked = builtins.map (
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
fullPath = syncthingPublicKeyPath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then machine else null
|
|
||||||
) machines;
|
|
||||||
syncthingPublicKeyMachines = lib.filter (machine: machine != null) syncthingPublicKeysUnchecked;
|
|
||||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
|
||||||
networkIpsUnchecked = builtins.map (
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
fullPath = zerotierIpMachinePath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then machine else null
|
|
||||||
) machines;
|
|
||||||
networkIpMachines = lib.filter (machine: machine != null) networkIpsUnchecked;
|
|
||||||
devices = builtins.map (machine: {
|
|
||||||
name = machine;
|
|
||||||
value = {
|
|
||||||
name = machine;
|
|
||||||
id = (lib.removeSuffix "\n" (builtins.readFile (syncthingPublicKeyPath machine)));
|
|
||||||
addresses =
|
|
||||||
[ "dynamic" ]
|
|
||||||
++ (
|
|
||||||
if (lib.elem machine networkIpMachines) then
|
|
||||||
[ "tcp://[${(lib.removeSuffix "\n" (builtins.readFile (zerotierIpMachinePath machine)))}]:22000" ]
|
|
||||||
else
|
|
||||||
[ ]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}) syncthingPublicKeyMachines;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.syncthing-static-peers = {
|
|
||||||
excludeMachines = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
example = [ config.clan.core.machineName ];
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Machines that should not be added.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config.services.syncthing.settings.devices = (builtins.listToAttrs devices);
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
{
|
|
||||||
# Syncthing ports: 8384 for remote access to GUI
|
|
||||||
# 22000 TCP and/or UDP for sync traffic
|
|
||||||
# 21027/UDP for discovery
|
|
||||||
# source: https://docs.syncthing.net/users/firewall.html
|
|
||||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [
|
|
||||||
8384
|
|
||||||
22000
|
|
||||||
];
|
|
||||||
networking.firewall.allowedTCPPorts = [ 8384 ];
|
|
||||||
networking.firewall.interfaces."zt+".allowedUDPPorts = [
|
|
||||||
22000
|
|
||||||
21027
|
|
||||||
];
|
|
||||||
|
|
||||||
# Activates inotify compatibility on syncthing
|
|
||||||
# use mkOverride 900 here as it otherwise would collide with the default of the
|
|
||||||
# upstream nixos xserver.nix
|
|
||||||
boot.kernel.sysctl."fs.inotify.max_user_watches" = lib.mkOverride 900 524288;
|
|
||||||
|
|
||||||
services.syncthing = {
|
|
||||||
enable = true;
|
|
||||||
configDir = "/var/lib/syncthing";
|
|
||||||
group = "syncthing";
|
|
||||||
|
|
||||||
key = lib.mkDefault config.clan.core.facts.services.syncthing.secret."syncthing.key".path or null;
|
|
||||||
cert = lib.mkDefault config.clan.core.facts.services.syncthing.secret."syncthing.cert".path or null;
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.facts.services.syncthing = {
|
|
||||||
secret."syncthing.key" = { };
|
|
||||||
secret."syncthing.cert" = { };
|
|
||||||
public."syncthing.pub" = { };
|
|
||||||
generator.path = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.gnugrep
|
|
||||||
pkgs.syncthing
|
|
||||||
];
|
|
||||||
generator.script = ''
|
|
||||||
syncthing generate --config "$secrets"
|
|
||||||
mv "$secrets"/key.pem "$secrets"/syncthing.key
|
|
||||||
mv "$secrets"/cert.pem "$secrets"/syncthing.cert
|
|
||||||
cat "$secrets"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$facts"/syncthing.pub
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
---
|
A secure, file synchronization app for devices over networks, offering a private alternative to cloud services.
|
||||||
description = "A secure, file synchronization app for devices over networks, offering a private alternative to cloud services."
|
|
||||||
---
|
---
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -7,14 +7,10 @@
|
|||||||
{
|
{
|
||||||
options.clan.syncthing = {
|
options.clan.syncthing = {
|
||||||
id = lib.mkOption {
|
id = lib.mkOption {
|
||||||
description = ''
|
|
||||||
The ID of the machine.
|
|
||||||
It is generated automatically by default.
|
|
||||||
'';
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
example = "BABNJY4-G2ICDLF-QQEG7DD-N3OBNGF-BCCOFK6-MV3K7QJ-2WUZHXS-7DTW4AS";
|
example = "BABNJY4-G2ICDLF-QQEG7DD-N3OBNGF-BCCOFK6-MV3K7QJ-2WUZHXS-7DTW4AS";
|
||||||
default = config.clan.core.facts.services.syncthing.public."syncthing.pub".value or null;
|
default = config.clanCore.facts.services.syncthing.public."syncthing.pub".value or null;
|
||||||
defaultText = "config.clan.core.facts.services.syncthing.public.\"syncthing.pub\".value";
|
defaultText = "config.clanCore.facts.services.syncthing.public.\"syncthing.pub\".value";
|
||||||
};
|
};
|
||||||
introducer = lib.mkOption {
|
introducer = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
@ -98,7 +94,7 @@
|
|||||||
settings = {
|
settings = {
|
||||||
options = {
|
options = {
|
||||||
urAccepted = -1;
|
urAccepted = -1;
|
||||||
allowedNetworks = [ config.clan.core.networking.zerotier.subnet ];
|
allowedNetworks = [ config.clan.networking.zerotier.subnet ];
|
||||||
};
|
};
|
||||||
devices =
|
devices =
|
||||||
{ }
|
{ }
|
||||||
@ -123,7 +119,7 @@
|
|||||||
getPendingDevices = "/rest/cluster/pending/devices";
|
getPendingDevices = "/rest/cluster/pending/devices";
|
||||||
postNewDevice = "/rest/config/devices";
|
postNewDevice = "/rest/config/devices";
|
||||||
SharedFolderById = "/rest/config/folders/";
|
SharedFolderById = "/rest/config/folders/";
|
||||||
apiKey = config.clan.core.facts.services.syncthing.secret."syncthing.api".path or null;
|
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
|
||||||
in
|
in
|
||||||
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||||
description = "Syncthing auto accept devices";
|
description = "Syncthing auto accept devices";
|
||||||
@ -165,7 +161,7 @@
|
|||||||
|
|
||||||
systemd.services.syncthing-init-api-key =
|
systemd.services.syncthing-init-api-key =
|
||||||
let
|
let
|
||||||
apiKey = config.clan.core.facts.services.syncthing.secret."syncthing.api".path or null;
|
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
|
||||||
in
|
in
|
||||||
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||||
description = "Set the api key";
|
description = "Set the api key";
|
||||||
@ -187,7 +183,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clan.core.facts.services.syncthing = {
|
clanCore.facts.services.syncthing = {
|
||||||
secret."syncthing.key" = { };
|
secret."syncthing.key" = { };
|
||||||
secret."syncthing.cert" = { };
|
secret."syncthing.cert" = { };
|
||||||
secret."syncthing.api" = { };
|
secret."syncthing.api" = { };
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
---
|
Modern web IRC client
|
||||||
description = "Modern web IRC client"
|
|
||||||
---
|
---
|
||||||
|
@ -11,5 +11,5 @@ _: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clan.core.state.thelounde.folders = [ "/var/lib/thelounge" ];
|
clanCore.state.thelounde.folders = [ "/var/lib/thelounge" ];
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
---
|
This module sets the `clan.lol` and `nix-community` cache up as a trusted cache.
|
||||||
description = "This module sets the `clan.lol` and `nix-community` cache up as a trusted cache."
|
|
||||||
----
|
----
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user