forked from clan/clan-core
Compare commits
2 Commits
main
...
add-serial
Author | SHA1 | Date | |
---|---|---|---|
828f0f5d27 | |||
80c70873ad |
12
.envrc
12
.envrc
@ -1,11 +1,5 @@
|
||||
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="
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 2.5.1; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.5.1/direnvrc" "sha256-puRzug17Ed4JFS2wbpqa3k764QV6xPP6O3A/ez/JpOM="
|
||||
fi
|
||||
|
||||
watch_file .direnv/selected-shell
|
||||
|
||||
if [ -e .direnv/selected-shell ]; then
|
||||
use flake .#$(cat .direnv/selected-shell)
|
||||
else
|
||||
use flake
|
||||
fi
|
||||
use flake
|
||||
|
12
.gitea/workflows/checks-impure.yaml
Normal file
12
.gitea/workflows/checks-impure.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
name: checks-impure
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: main
|
||||
jobs:
|
||||
test:
|
||||
if: ${{ github.actor != 'ui-asset-bot' }}
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: nix run .#impure-checks
|
@ -2,11 +2,11 @@ name: checks
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: main
|
||||
jobs:
|
||||
checks-impure:
|
||||
test:
|
||||
if: ${{ github.actor != 'ui-asset-bot' }}
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: nix run .#impure-checks
|
||||
- run: nix run --refresh github:Mic92/nix-fast-build -- --no-nom
|
||||
|
@ -1,13 +0,0 @@
|
||||
name: deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy-docs:
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: nix run .#deploy-docs
|
||||
env:
|
||||
SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }}
|
68
.gitea/workflows/ui_assets.yaml
Normal file
68
.gitea/workflows/ui_assets.yaml
Normal file
@ -0,0 +1,68 @@
|
||||
name: assets1
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
test:
|
||||
if: ${{ github.actor != 'ui-asset-bot' }}
|
||||
runs-on: nix
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get changed files using defaults
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v32
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Check if UI files are in the list of modified files
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
echo "Modified files: $MODIFIED_FILES"
|
||||
if echo "$MODIFIED_FILES" | grep -q "pkgs/ui/" \
|
||||
|| echo "$MODIFIED_FILES" | grep -q ".gitea/workflows/ui_assets.yaml"; then
|
||||
|
||||
echo "UI files have changed"
|
||||
./pkgs/ui/nix/update-ui-assets.sh
|
||||
|
||||
|
||||
# git push if we have a diff
|
||||
if [[ -n $(git diff) ]]; then
|
||||
|
||||
DEPS=$(nix shell --inputs-from '.#' "nixpkgs#coreutils-full" -c bash -c "echo \$PATH")
|
||||
export PATH=$PATH:$DEPS
|
||||
|
||||
# Setup git config
|
||||
git config --global user.email "ui-asset-bot@clan.lol"
|
||||
git config --global user.name "ui-asset-bot"
|
||||
|
||||
################################################
|
||||
# #
|
||||
# WARNING: SECRETS ARE BEING PROCESSED HERE. #
|
||||
# !DO NOT LOG THIS! #
|
||||
# #
|
||||
################################################
|
||||
set +x
|
||||
AUTH_TOKEN=$(echo -n "x-access-token:$GITEA_TOKEN" | base64)
|
||||
git config http."$GITHUB_SERVER_URL/".extraheader "AUTHORIZATION: basic $AUTH_TOKEN"
|
||||
set -x
|
||||
################################################
|
||||
# #
|
||||
# END OF SECRETS AREA #
|
||||
# #
|
||||
################################################
|
||||
|
||||
# Commit and push
|
||||
git commit -am "update ui-assets.nix"
|
||||
|
||||
echo "Current branch: $GITHUB_REF_NAME"
|
||||
git push origin HEAD:$GITHUB_REF_NAME
|
||||
fi
|
||||
else
|
||||
echo "No UI files changed. Skipping asset build and push"
|
||||
fi
|
||||
env:
|
||||
MODIFIED_FILES: ${{ steps.changed-files.outputs.modified_files }}
|
||||
GITEA_TOKEN: ${{ secrets.BOT_ACCESS_TOKEN }}
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
.direnv
|
||||
***/.hypothesis
|
||||
out.log
|
||||
.coverage.*
|
||||
**/qubeclan
|
||||
**/testdir
|
||||
@ -9,12 +8,9 @@ example_clan
|
||||
result*
|
||||
/pkgs/clan-cli/clan_cli/nixpkgs
|
||||
/pkgs/clan-cli/clan_cli/webui/assets
|
||||
/machines
|
||||
nixos.qcow2
|
||||
**/*.glade~
|
||||
/docs/out
|
||||
|
||||
# dream2nix
|
||||
.dream2nix
|
||||
|
||||
# python
|
||||
__pycache__
|
||||
@ -24,15 +20,3 @@ __pycache__
|
||||
.reports
|
||||
.ruff_cache
|
||||
htmlcov
|
||||
|
||||
# flatpak
|
||||
.flatpak-builder
|
||||
build
|
||||
build-dir
|
||||
repo
|
||||
.env
|
||||
|
||||
# node
|
||||
node_modules
|
||||
dist
|
||||
.webui
|
@ -1,21 +0,0 @@
|
||||
# Contributing to Clan
|
||||
|
||||
## Live-reloading documentation
|
||||
|
||||
Enter the `docs` directory:
|
||||
|
||||
```shell-session
|
||||
cd docs
|
||||
```
|
||||
|
||||
Enter the development shell or enable `direnv`:
|
||||
|
||||
```shell-session
|
||||
direnv allow
|
||||
```
|
||||
|
||||
Run a local server:
|
||||
|
||||
```shell-session
|
||||
mkdocs serve
|
||||
```
|
46
README.md
46
README.md
@ -1,45 +1,23 @@
|
||||
# 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 houses all the essential packages, NixOS modules, CLI tools, and tests you need to contribute and work with the cLAN project.
|
||||
|
||||
## Why Clan?
|
||||
## Getting Started
|
||||
|
||||
Our mission is simple: to democratize computing by providing tools that empower users, foster innovation, and challenge outdated paradigms. Clan represents our contribution to a future where technology serves humanity, not the other way around. By participating in Clan, you're joining a movement dedicated to creating a secure, user-empowered digital future.
|
||||
If you're new to cLAN and eager to dive in, start with our quickstart guide:
|
||||
|
||||
## Features of Clan
|
||||
- **Quickstart Guide**: Check out [quickstart.md](docs/quickstart.md) to get up and running with cLAN in no time.
|
||||
|
||||
- **Full-Stack System Deployment:** Utilize Clan’s toolkit alongside Nix's reliability to build and manage systems effortlessly.
|
||||
- **Overlay Networks:** Secure, private communication channels between devices.
|
||||
- **Virtual Machine Integration:** Seamless operation of VM applications within the main operating system.
|
||||
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
||||
- **Intuitive Secret Management:** Simplified encryption and password management processes.
|
||||
## Managing Secrets
|
||||
|
||||
## Getting Started with Clan
|
||||
Security is paramount, and cLAN provides guidelines for handling secrets effectively:
|
||||
|
||||
If you're new to Clan and eager to dive in, start with our quickstart guide and explore the core functionalities that Clan offers:
|
||||
- **Secrets Management**: Learn how to manage secrets securely by reading [secrets-management.md](docs/secrets-management.md).
|
||||
|
||||
- **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.
|
||||
## Contributing to cLAN
|
||||
|
||||
### Managing Secrets
|
||||
We welcome contributions from the community, and we've prepared a comprehensive guide to help you get started:
|
||||
|
||||
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
|
||||
|
||||
- **Secrets Management**: Securely manage secrets by consulting [secrets](https://docs.clan.lol/getting-started/secrets/)<!-- [secrets.md](docs/site/getting-started/secrets.md) -->.
|
||||
|
||||
### Contributing to Clan
|
||||
|
||||
The Clan project thrives on community contributions. We welcome everyone to contribute and collaborate:
|
||||
|
||||
- **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
|
||||
|
||||
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
|
||||
|
||||
Connect with us and the Clan community for support and discussion:
|
||||
|
||||
- [Matrix channel](https://matrix.to/#/#clan:lassul.us) for live discussions.
|
||||
- IRC bridges (coming soon) for real-time chat support.
|
||||
- **Contribution Guidelines**: Find out how to contribute and make a meaningful impact on the cLAN project by reading [contributing.md](docs/contributing.md).
|
||||
|
||||
Whether you're a newcomer or a seasoned developer, we look forward to your contributions and collaboration on the cLAN project. Let's build amazing things together!
|
||||
|
@ -1,178 +0,0 @@
|
||||
{ self, ... }:
|
||||
{
|
||||
clan.machines.test-backup = {
|
||||
imports = [ self.nixosModules.test-backup ];
|
||||
fileSystems."/".device = "/dev/null";
|
||||
boot.loader.grub.device = "/dev/null";
|
||||
};
|
||||
flake.nixosModules = {
|
||||
test-backup =
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
self
|
||||
pkgs.stdenv.drvPath
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-backup.config.system.clan.deployment.file
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
self.clanModules.borgbackup
|
||||
self.clanModules.localbackup
|
||||
self.clanModules.sshd
|
||||
];
|
||||
clan.networking.targetHost = "machine";
|
||||
networking.hostName = "machine";
|
||||
services.openssh.settings.UseDns = false;
|
||||
|
||||
programs.ssh.knownHosts = {
|
||||
machine.hostNames = [ "machine" ];
|
||||
machine.publicKey = builtins.readFile ../lib/ssh/pubkey;
|
||||
};
|
||||
|
||||
users.users.root.openssh.authorizedKeys.keyFiles = [ ../lib/ssh/pubkey ];
|
||||
|
||||
systemd.tmpfiles.settings."vmsecrets" = {
|
||||
"/root/.ssh/id_ed25519" = {
|
||||
C.argument = "${../lib/ssh/privkey}";
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
"/etc/secrets/ssh.id_ed25519" = {
|
||||
C.argument = "${../lib/ssh/privkey}";
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
"/etc/secrets/borgbackup.ssh" = {
|
||||
C.argument = "${../lib/ssh/privkey}";
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
"/etc/secrets/borgbackup.repokey" = {
|
||||
C.argument = builtins.toString (pkgs.writeText "repokey" "repokey12345");
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
};
|
||||
clanCore.facts.secretStore = "vm";
|
||||
|
||||
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";
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
};
|
||||
system.extraDependencies = dependencies;
|
||||
clanCore.state.test-backups.folders = [ "/var/test-backups" ];
|
||||
|
||||
clanCore.state.test-service = {
|
||||
preRestoreCommand = "pre-restore-command";
|
||||
postRestoreCommand = "post-restore-command";
|
||||
folders = [ "/var/test-service" ];
|
||||
};
|
||||
clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";
|
||||
|
||||
fileSystems."/mnt/external-disk" = {
|
||||
device = "/dev/vdb"; # created in tests with virtualisation.emptyDisks
|
||||
autoFormat = true;
|
||||
fsType = "ext4";
|
||||
options = [
|
||||
"defaults"
|
||||
"noauto"
|
||||
];
|
||||
};
|
||||
|
||||
clan.localbackup.targets.hdd = {
|
||||
directory = "/mnt/external-disk";
|
||||
preMountHook = ''
|
||||
touch /run/mount-external-disk
|
||||
'';
|
||||
postUnmountHook = ''
|
||||
touch /run/unmount-external-disk
|
||||
'';
|
||||
};
|
||||
|
||||
services.borgbackup.repos.test-backups = {
|
||||
path = "/var/lib/borgbackup/test-backups";
|
||||
authorizedKeys = [ (builtins.readFile ../lib/ssh/pubkey) ];
|
||||
};
|
||||
};
|
||||
};
|
||||
perSystem =
|
||||
{ nodes, pkgs, ... }:
|
||||
{
|
||||
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||
test-backups = (import ../lib/test-base.nix) {
|
||||
name = "test-backups";
|
||||
nodes.machine = {
|
||||
imports = [
|
||||
self.nixosModules.clanCore
|
||||
self.nixosModules.test-backup
|
||||
];
|
||||
virtualisation.emptyDiskImages = [ 256 ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
start_all()
|
||||
|
||||
# dummy data
|
||||
machine.succeed("mkdir -p /var/test-backups /var/test-service")
|
||||
machine.succeed("echo testing > /var/test-backups/somefile")
|
||||
|
||||
# create
|
||||
machine.succeed("clan backups create --debug --flake ${self} test-backup")
|
||||
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
|
||||
machine.succeed("test -f /run/mount-external-disk")
|
||||
machine.succeed("test -f /run/unmount-external-disk")
|
||||
|
||||
# list
|
||||
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
|
||||
out = machine.succeed("clan backups list --debug --flake ${self} test-backup").strip()
|
||||
print(out)
|
||||
assert backup_id in out, f"backup {backup_id} not found in {out}"
|
||||
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
|
||||
assert localbackup_id in out, "localbackup not found in {out}"
|
||||
|
||||
## borgbackup restore
|
||||
machine.succeed("rm -f /var/test-backups/somefile")
|
||||
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
||||
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/post-restore-command")
|
||||
|
||||
## localbackup restore
|
||||
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")
|
||||
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/post-restore-command")
|
||||
'';
|
||||
} { inherit pkgs self; };
|
||||
};
|
||||
};
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
(import ../lib/test-base.nix) (
|
||||
{ ... }:
|
||||
{
|
||||
(import ../lib/container-test.nix) ({ ... }: {
|
||||
name = "borgbackup";
|
||||
|
||||
nodes.machine =
|
||||
{ self, pkgs, ... }:
|
||||
{
|
||||
nodes.machine = { self, ... }: {
|
||||
imports = [
|
||||
self.clanModules.borgbackup
|
||||
self.nixosModules.clanCore
|
||||
{
|
||||
services.openssh.enable = true;
|
||||
services.borgbackup.repos.testrepo = {
|
||||
authorizedKeys = [ (builtins.readFile ../lib/ssh/pubkey) ];
|
||||
authorizedKeys = [
|
||||
(builtins.readFile ./borg_test.pub)
|
||||
];
|
||||
};
|
||||
}
|
||||
{
|
||||
@ -20,25 +18,13 @@
|
||||
clanCore.clanDir = ./.;
|
||||
clanCore.state.testState.folders = [ "/etc/state" ];
|
||||
environment.etc.state.text = "hello world";
|
||||
systemd.tmpfiles.settings."vmsecrets" = {
|
||||
"/etc/secrets/borgbackup.ssh" = {
|
||||
C.argument = "${../lib/ssh/privkey}";
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "root";
|
||||
clan.borgbackup = {
|
||||
enable = true;
|
||||
destinations.test = {
|
||||
repo = "borg@localhost:.";
|
||||
rsh = "ssh -i ${./borg_test} -o StrictHostKeyChecking=no";
|
||||
};
|
||||
};
|
||||
"/etc/secrets/borgbackup.repokey" = {
|
||||
C.argument = builtins.toString (pkgs.writeText "repokey" "repokey12345");
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
};
|
||||
clanCore.facts.secretStore = "vm";
|
||||
|
||||
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
|
||||
}
|
||||
];
|
||||
};
|
||||
@ -47,5 +33,4 @@
|
||||
machine.systemctl("start --wait borgbackup-job-test.service")
|
||||
assert "machine-test" in machine.succeed("BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes /run/current-system/sw/bin/borg-job-test list")
|
||||
'';
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -1,11 +1,7 @@
|
||||
(import ../lib/container-test.nix) (
|
||||
{ ... }:
|
||||
{
|
||||
(import ../lib/container-test.nix) ({ ... }: {
|
||||
name = "secrets";
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
nodes.machine = { ... }: {
|
||||
networking.hostName = "machine";
|
||||
services.openssh.enable = true;
|
||||
services.openssh.startWhenNeeded = false;
|
||||
@ -15,5 +11,4 @@
|
||||
machine.succeed("systemctl status sshd")
|
||||
machine.wait_for_unit("sshd")
|
||||
'';
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -1,11 +1,7 @@
|
||||
(import ../lib/container-test.nix) (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
(import ../lib/container-test.nix) ({ pkgs, ... }: {
|
||||
name = "secrets";
|
||||
|
||||
nodes.machine =
|
||||
{ self, ... }:
|
||||
{
|
||||
nodes.machine = { self, ... }: {
|
||||
imports = [
|
||||
self.clanModules.deltachat
|
||||
self.nixosModules.clanCore
|
||||
@ -25,5 +21,4 @@
|
||||
# smtp
|
||||
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 25")
|
||||
'';
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -1,34 +1,10 @@
|
||||
{ self, ... }:
|
||||
{
|
||||
{ self, ... }: {
|
||||
imports = [
|
||||
./impure/flake-module.nix
|
||||
./backups/flake-module.nix
|
||||
./installation/flake-module.nix
|
||||
./flash/flake-module.nix
|
||||
];
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
self',
|
||||
...
|
||||
}:
|
||||
{
|
||||
perSystem = { pkgs, lib, self', ... }: {
|
||||
checks =
|
||||
let
|
||||
# ensure all options can be rendered after importing clan into nixos
|
||||
renderClanOptions =
|
||||
let
|
||||
docs = pkgs.nixosOptionsDoc {
|
||||
options =
|
||||
(pkgs.nixos {
|
||||
imports = [ self.nixosModules.clanCore ];
|
||||
clanCore.clanDir = ./.;
|
||||
}).options;
|
||||
warningsAreErrors = false;
|
||||
};
|
||||
in
|
||||
docs.optionsJSON;
|
||||
nixosTestArgs = {
|
||||
# reference to nixpkgs for the current system
|
||||
inherit pkgs;
|
||||
@ -40,24 +16,20 @@
|
||||
secrets = import ./secrets nixosTestArgs;
|
||||
container = import ./container nixosTestArgs;
|
||||
deltachat = import ./deltachat nixosTestArgs;
|
||||
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
||||
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
||||
meshnamed = import ./meshnamed nixosTestArgs;
|
||||
borgbackup = import ./borgbackup nixosTestArgs;
|
||||
syncthing = import ./syncthing nixosTestArgs;
|
||||
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
|
||||
};
|
||||
schemaTests = pkgs.callPackages ./schemas.nix {
|
||||
inherit self;
|
||||
};
|
||||
|
||||
flakeOutputs =
|
||||
lib.mapAttrs' (
|
||||
name: config: lib.nameValuePair "nixos-${name}" config.config.system.build.toplevel
|
||||
) self.nixosConfigurations
|
||||
flakeOutputs = lib.mapAttrs' (name: config: lib.nameValuePair "nixos-${name}" config.config.system.build.toplevel) self.nixosConfigurations
|
||||
// lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages
|
||||
// lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells
|
||||
// lib.mapAttrs' (name: config: lib.nameValuePair "home-manager-${name}" config.activation-script) (
|
||||
self'.legacyPackages.homeConfigurations or { }
|
||||
);
|
||||
// lib.mapAttrs' (name: config: lib.nameValuePair "home-manager-${name}" config.activation-script) (self'.legacyPackages.homeConfigurations or { });
|
||||
in
|
||||
{ inherit renderClanOptions; } // nixosTests // flakeOutputs;
|
||||
nixosTests // schemaTests // flakeOutputs;
|
||||
legacyPackages = {
|
||||
nixosTests =
|
||||
let
|
||||
|
@ -1,50 +0,0 @@
|
||||
{ self, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
nodes,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
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.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.clan.deployment.file
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
# Currently disabled...
|
||||
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||
flash = (import ../lib/test-base.nix) {
|
||||
name = "flash";
|
||||
nodes.target = {
|
||||
virtualisation.emptyDiskImages = [ 4096 ];
|
||||
virtualisation.memorySize = 3000;
|
||||
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.succeed("clan flash --debug --flake ${../..} --yes --disk main /dev/vdb test_install_machine")
|
||||
'';
|
||||
} { inherit pkgs self; };
|
||||
};
|
||||
};
|
||||
}
|
@ -1,24 +1,66 @@
|
||||
{
|
||||
perSystem =
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
{ ... }: {
|
||||
perSystem = { pkgs, lib, ... }: {
|
||||
packages = rec {
|
||||
# a script that executes all other checks
|
||||
packages.impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
||||
impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
unset CLAN_DIR
|
||||
|
||||
export PATH="${
|
||||
lib.makeBinPath [
|
||||
export PATH="${lib.makeBinPath [
|
||||
pkgs.gitMinimal
|
||||
pkgs.nix
|
||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
||||
]
|
||||
}"
|
||||
]}"
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
cd "$ROOT/pkgs/clan-cli"
|
||||
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 -m impure -s ./tests $@"
|
||||
'';
|
||||
|
||||
runMockApi = pkgs.writeShellScriptBin "run-mock-api" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
export PATH="${lib.makeBinPath [
|
||||
pkgs.gitMinimal
|
||||
pkgs.nix
|
||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
||||
pkgs.coreutils
|
||||
pkgs.procps
|
||||
]}"
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
cd "$ROOT/pkgs/clan-cli"
|
||||
nix develop "$ROOT#clan-cli" -c bash -c 'TMPDIR=/tmp clan webui --no-open --port 5757'
|
||||
'';
|
||||
|
||||
|
||||
runSchemaTests = pkgs.writeShellScriptBin "runSchemaTests" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
${runMockApi}/bin/run-mock-api &
|
||||
MOCK_API_PID=$!
|
||||
echo "Started mock api with pid $MOCK_API_PID"
|
||||
function cleanup {
|
||||
echo "Stopping server..."
|
||||
pkill -9 -f "python -m clan webui --no-open --port 5757"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
export PATH="${lib.makeBinPath [
|
||||
pkgs.gitMinimal
|
||||
pkgs.nix
|
||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
||||
pkgs.procps
|
||||
pkgs.coreutils
|
||||
]}"
|
||||
|
||||
sleep 3
|
||||
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
cd "$ROOT/pkgs/clan-cli"
|
||||
nix develop "$ROOT#clan-cli" -c bash -c 'TMPDIR=/tmp st auth login RHtr8nLtz77tqRP8yUGyf-Flv_9SLI'
|
||||
nix develop "$ROOT#clan-cli" -c bash -c 'TMPDIR=/tmp st run http://localhost:5757/openapi.json --experimental=openapi-3.1 --report --workers 8 --max-response-time=50 --request-timeout=1000 -M GET'
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -1,114 +0,0 @@
|
||||
{ self, lib, ... }:
|
||||
{
|
||||
clan.machines.test_install_machine = {
|
||||
clan.networking.targetHost = "test_install_machine";
|
||||
fileSystems."/".device = lib.mkDefault "/dev/vdb";
|
||||
boot.loader.grub.device = lib.mkDefault "/dev/vdb";
|
||||
|
||||
imports = [ self.nixosModules.test_install_machine ];
|
||||
};
|
||||
flake.nixosModules = {
|
||||
test_install_machine =
|
||||
{ lib, modulesPath, ... }:
|
||||
{
|
||||
imports = [
|
||||
self.clanModules.disk-layouts
|
||||
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
clan.disk-layouts.singleDiskExt4.device = "/dev/vdb";
|
||||
|
||||
environment.etc."install-successful".text = "ok";
|
||||
|
||||
boot.consoleLogLevel = lib.mkForce 100;
|
||||
boot.kernelParams = [ "boot.shell_on_fail" ];
|
||||
};
|
||||
};
|
||||
perSystem =
|
||||
{
|
||||
nodes,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
self
|
||||
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.clan.deployment.file
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.nixos-anywhere
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||
test-installation = (import ../lib/test-base.nix) {
|
||||
name = "test-installation";
|
||||
nodes.target = {
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keyFiles = [ ../lib/ssh/pubkey ];
|
||||
system.nixos.variant_id = "installer";
|
||||
virtualisation.emptyDiskImages = [ 4096 ];
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
};
|
||||
};
|
||||
nodes.client = {
|
||||
environment.systemPackages = [
|
||||
self.packages.${pkgs.system}.clan-cli
|
||||
] ++ self.packages.${pkgs.system}.clan-cli.runtimeDependencies;
|
||||
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||
virtualisation.memorySize = 2048;
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
};
|
||||
system.extraDependencies = dependencies;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def create_test_machine(oldmachine=None, args={}): # taken from <nixpkgs/nixos/tests/installer.nix>
|
||||
startCommand = "${pkgs.qemu_test}/bin/qemu-kvm"
|
||||
startCommand += " -cpu max -m 1024 -virtfs local,path=/nix/store,security_model=none,mount_tag=nix-store"
|
||||
startCommand += f' -drive file={oldmachine.state_dir}/empty0.qcow2,id=drive1,if=none,index=1,werror=report'
|
||||
startCommand += ' -device virtio-blk-pci,drive=drive1'
|
||||
machine = create_machine({
|
||||
"startCommand": startCommand,
|
||||
} | args)
|
||||
driver.machines.append(machine)
|
||||
return machine
|
||||
|
||||
start_all()
|
||||
|
||||
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
||||
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
||||
|
||||
client.succeed("clan machines install --debug --flake ${../..} --yes test_install_machine root@target >&2")
|
||||
try:
|
||||
target.shutdown()
|
||||
except BrokenPipeError:
|
||||
# qemu has already exited
|
||||
pass
|
||||
|
||||
new_machine = create_test_machine(oldmachine=target, args={ "name": "new_machine" })
|
||||
assert(new_machine.succeed("cat /etc/install-successful").strip() == "ok")
|
||||
'';
|
||||
} { inherit pkgs self; };
|
||||
};
|
||||
};
|
||||
}
|
@ -1,23 +1,17 @@
|
||||
{
|
||||
hostPkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{ hostPkgs, lib, config, ... }:
|
||||
let
|
||||
testDriver = hostPkgs.python3.pkgs.callPackage ./package.nix {
|
||||
inherit (config) extraPythonPackages;
|
||||
inherit (hostPkgs.pkgs) util-linux systemd;
|
||||
};
|
||||
containers = map (m: m.system.build.toplevel) (lib.attrValues config.nodes);
|
||||
pythonizeName =
|
||||
name:
|
||||
pythonizeName = name:
|
||||
let
|
||||
head = lib.substring 0 1 name;
|
||||
tail = lib.substring 1 (-1) name;
|
||||
in
|
||||
(if builtins.match "[A-z_]" head == null then "_" else head)
|
||||
+ lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
|
||||
(if builtins.match "[A-z_]" head == null then "_" else head) +
|
||||
lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
|
||||
nodeHostNames =
|
||||
let
|
||||
nodesList = map (c: c.system.name) (lib.attrValues config.nodes);
|
||||
@ -27,8 +21,7 @@ let
|
||||
pythonizedNames = map pythonizeName nodeHostNames;
|
||||
in
|
||||
{
|
||||
driver = lib.mkForce (
|
||||
hostPkgs.runCommand "nixos-test-driver-${config.name}"
|
||||
driver = lib.mkForce (hostPkgs.runCommand "nixos-test-driver-${config.name}"
|
||||
{
|
||||
nativeBuildInputs = [
|
||||
hostPkgs.makeWrapper
|
||||
@ -68,11 +61,9 @@ in
|
||||
wrapProgram $out/bin/nixos-test-driver \
|
||||
${lib.concatStringsSep " " (map (name: "--add-flags '--container ${name}'") containers)} \
|
||||
--add-flags "--test-script '$out/test-script'"
|
||||
''
|
||||
);
|
||||
'');
|
||||
|
||||
test = lib.mkForce (
|
||||
lib.lazyDerivation {
|
||||
test = lib.mkForce (lib.lazyDerivation {
|
||||
# lazyDerivation improves performance when only passthru items and/or meta are used.
|
||||
derivation = hostPkgs.stdenv.mkDerivation {
|
||||
name = "vm-test-run-${config.name}";
|
||||
@ -93,6 +84,5 @@ in
|
||||
meta = config.meta;
|
||||
};
|
||||
inherit (config) passthru meta;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -1,18 +1,8 @@
|
||||
{
|
||||
extraPythonPackages,
|
||||
python3Packages,
|
||||
buildPythonApplication,
|
||||
setuptools,
|
||||
util-linux,
|
||||
systemd,
|
||||
}:
|
||||
{ extraPythonPackages, python3Packages, buildPythonApplication, setuptools, util-linux, systemd }:
|
||||
buildPythonApplication {
|
||||
pname = "test-driver";
|
||||
version = "0.0.1";
|
||||
propagatedBuildInputs = [
|
||||
util-linux
|
||||
systemd
|
||||
] ++ extraPythonPackages python3Packages;
|
||||
propagatedBuildInputs = [ util-linux systemd ] ++ extraPythonPackages python3Packages;
|
||||
nativeBuildInputs = [ setuptools ];
|
||||
format = "pyproject";
|
||||
src = ./.;
|
||||
|
@ -19,8 +19,8 @@ test_driver = ["py.typed"]
|
||||
target-version = "py311"
|
||||
line-length = 88
|
||||
|
||||
lint.select = [ "E", "F", "I", "U", "N", "RUF", "ANN", "A" ]
|
||||
lint.ignore = ["E501", "ANN101", "ANN401", "A003"]
|
||||
select = [ "E", "F", "I", "U", "N", "RUF", "ANN", "A" ]
|
||||
ignore = ["E501", "ANN101", "ANN401", "A003"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
|
@ -258,7 +258,7 @@ class Driver:
|
||||
|
||||
self.machines = []
|
||||
for container in containers:
|
||||
name_match = re.match(r".*-nixos-system-(.+)-(.+)", container.name)
|
||||
name_match = re.match(r".*-nixos-system-(.+)-\d.+", container.name)
|
||||
if not name_match:
|
||||
raise ValueError(f"Unable to extract hostname from {container.name}")
|
||||
name = name_match.group(1)
|
||||
|
@ -1,16 +1,16 @@
|
||||
test:
|
||||
{ pkgs, self, ... }:
|
||||
{ pkgs
|
||||
, self
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
nixos-lib = import (pkgs.path + "/nixos/lib") { };
|
||||
in
|
||||
(nixos-lib.runTest (
|
||||
{ hostPkgs, ... }:
|
||||
{
|
||||
(nixos-lib.runTest ({ hostPkgs, ... }: {
|
||||
hostPkgs = pkgs;
|
||||
# speed-up evaluation
|
||||
defaults = {
|
||||
nix.package = pkgs.nixVersions.latest;
|
||||
documentation.enable = lib.mkDefault false;
|
||||
boot.isContainer = true;
|
||||
|
||||
@ -30,5 +30,4 @@ in
|
||||
test
|
||||
./container-driver/module.nix
|
||||
];
|
||||
}
|
||||
)).config.result
|
||||
})).config.result
|
||||
|
@ -1,5 +1,8 @@
|
||||
test:
|
||||
{ pkgs, self, ... }:
|
||||
{ pkgs
|
||||
, self
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
nixos-lib = import (pkgs.path + "/nixos/lib") { };
|
||||
@ -7,13 +10,9 @@ in
|
||||
(nixos-lib.runTest {
|
||||
hostPkgs = pkgs;
|
||||
# speed-up evaluation
|
||||
defaults = {
|
||||
documentation.enable = lib.mkDefault false;
|
||||
nix.settings.min-free = 0;
|
||||
nix.package = pkgs.nixVersions.latest;
|
||||
};
|
||||
|
||||
defaults.documentation.enable = lib.mkDefault false;
|
||||
# to accept external dependencies such as disko
|
||||
node.specialArgs.self = self;
|
||||
imports = [ test ];
|
||||
}).config.result
|
||||
|
||||
|
@ -1,37 +0,0 @@
|
||||
(import ../lib/container-test.nix) (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "matrix-synapse";
|
||||
|
||||
nodes.machine =
|
||||
{ self, lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
self.clanModules.matrix-synapse
|
||||
self.nixosModules.clanCore
|
||||
{
|
||||
clanCore.machineName = "machine";
|
||||
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" = {
|
||||
enableACME = lib.mkForce false;
|
||||
forceSSL = lib.mkForce false;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
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 +0,0 @@
|
||||
registration_shared_secret: supersecret
|
21
checks/meshnamed/default.nix
Normal file
21
checks/meshnamed/default.nix
Normal file
@ -0,0 +1,21 @@
|
||||
(import ../lib/container-test.nix) ({ pkgs, ... }: {
|
||||
name = "meshnamed";
|
||||
|
||||
nodes.machine = { self, ... }: {
|
||||
imports = [
|
||||
self.nixosModules.clanCore
|
||||
{
|
||||
clanCore.machineName = "machine";
|
||||
clan.networking.meshnamed.networks.vpn.subnet = "fd43:7def:4b50:28d0:4e99:9347:3035:17ef/88";
|
||||
clanCore.clanDir = ./.;
|
||||
}
|
||||
];
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("meshnamed")
|
||||
out = machine.succeed("${pkgs.dnsutils}/bin/dig AAAA foo.7vbx332lkaunatuzsndtanix54.vpn @meshnamed +short")
|
||||
print(out)
|
||||
assert out.strip() == "fd43:7def:4b50:28d0:4e99:9347:3035:17ef"
|
||||
'';
|
||||
})
|
54
checks/schema.nix
Normal file
54
checks/schema.nix
Normal file
@ -0,0 +1,54 @@
|
||||
{ self, lib, inputs, ... }:
|
||||
let
|
||||
inherit (builtins)
|
||||
mapAttrs
|
||||
toJSON
|
||||
toFile
|
||||
;
|
||||
inherit (lib)
|
||||
mapAttrs'
|
||||
;
|
||||
clanLib = self.lib;
|
||||
clanModules = self.clanModules;
|
||||
|
||||
|
||||
in
|
||||
{
|
||||
perSystem = { pkgs, ... }:
|
||||
let
|
||||
baseModule = {
|
||||
imports =
|
||||
(import (inputs.nixpkgs + "/nixos/modules/module-list.nix"))
|
||||
++ [{
|
||||
nixpkgs.hostPlatform = pkgs.system;
|
||||
}];
|
||||
};
|
||||
|
||||
optionsFromModule = module:
|
||||
let
|
||||
evaled = lib.evalModules {
|
||||
modules = [ module baseModule ];
|
||||
};
|
||||
in
|
||||
evaled.options.clan.networking;
|
||||
|
||||
clanModuleSchemas =
|
||||
mapAttrs
|
||||
(_: module: clanLib.jsonschema.parseOptions (optionsFromModule module))
|
||||
clanModules;
|
||||
|
||||
mkTest = name: schema: pkgs.runCommand "schema-${name}" { } ''
|
||||
${pkgs.check-jsonschema}/bin/check-jsonschema \
|
||||
--check-metaschema ${toFile "schema-${name}" (toJSON schema)}
|
||||
touch $out
|
||||
'';
|
||||
in
|
||||
{
|
||||
checks = mapAttrs'
|
||||
(name: schema: {
|
||||
name = "schema-${name}";
|
||||
value = mkTest name schema;
|
||||
})
|
||||
clanModuleSchemas;
|
||||
};
|
||||
}
|
34
checks/schemas.nix
Normal file
34
checks/schemas.nix
Normal file
@ -0,0 +1,34 @@
|
||||
{ self, runCommand, check-jsonschema, pkgs, lib, ... }:
|
||||
let
|
||||
clanModules.clanCore = self.nixosModules.clanCore;
|
||||
|
||||
baseModule = {
|
||||
imports =
|
||||
(import (pkgs.path + "/nixos/modules/module-list.nix"))
|
||||
++ [{
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
}];
|
||||
};
|
||||
|
||||
optionsFromModule = module:
|
||||
let
|
||||
evaled = lib.evalModules {
|
||||
modules = [ module baseModule ];
|
||||
};
|
||||
in
|
||||
evaled.options.clan;
|
||||
|
||||
clanModuleSchemas = lib.mapAttrs (_: module: self.lib.jsonschema.parseOptions (optionsFromModule module)) clanModules;
|
||||
|
||||
mkTest = name: schema: runCommand "schema-${name}" { } ''
|
||||
${check-jsonschema}/bin/check-jsonschema \
|
||||
--check-metaschema ${builtins.toFile "schema-${name}" (builtins.toJSON schema)}
|
||||
touch $out
|
||||
'';
|
||||
in
|
||||
lib.mapAttrs'
|
||||
(name: schema: {
|
||||
name = "schema-${name}";
|
||||
value = mkTest name schema;
|
||||
})
|
||||
clanModuleSchemas
|
@ -1,14 +1,13 @@
|
||||
(import ../lib/test-base.nix) {
|
||||
name = "secrets";
|
||||
|
||||
nodes.machine =
|
||||
{ self, config, ... }:
|
||||
{
|
||||
environment.etc."privkey.age".source = ./key.age;
|
||||
imports = [ (self.nixosModules.clanCore) ];
|
||||
nodes.machine = { self, config, ... }: {
|
||||
imports = [
|
||||
(self.nixosModules.clanCore)
|
||||
];
|
||||
environment.etc."secret".source = config.sops.secrets.secret.path;
|
||||
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
|
||||
sops.age.keyFile = "/etc/privkey.age";
|
||||
sops.age.keyFile = ./key.age;
|
||||
|
||||
clanCore.clanDir = "${./.}";
|
||||
clanCore.machineName = "machine";
|
||||
|
@ -19,7 +19,7 @@
|
||||
"syncthing.key".source = ./introducer/introducer_test_key;
|
||||
"syncthing.api".source = ./introducer/introducer_test_api;
|
||||
};
|
||||
clanCore.facts.services.syncthing.secret."syncthing.api".path = "/etc/syncthing.api";
|
||||
clanCore.secrets.syncthing.secrets."syncthing.api".path = "/etc/syncthing.api";
|
||||
services.syncthing.cert = "/etc/syncthing.pam";
|
||||
services.syncthing.key = "/etc/syncthing.key";
|
||||
# Doesn't test zerotier!
|
||||
|
@ -1,35 +0,0 @@
|
||||
import ../lib/test-base.nix (
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "wayland-proxy-virtwl";
|
||||
|
||||
nodes.machine =
|
||||
{ self, ... }:
|
||||
{
|
||||
imports = [
|
||||
self.nixosModules.clanCore
|
||||
{
|
||||
clanCore.machineName = "machine";
|
||||
clanCore.clanDir = ./.;
|
||||
}
|
||||
];
|
||||
services.wayland-proxy-virtwl.enable = true;
|
||||
|
||||
virtualisation.qemu.options = [
|
||||
"-vga none -device virtio-gpu-rutabaga,cross-domain=on,hostmem=4G,wsi=headless"
|
||||
];
|
||||
|
||||
virtualisation.qemu.package = lib.mkForce pkgs.qemu_kvm;
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
# use machinectl
|
||||
machine.succeed("machinectl shell .host ${config.nodes.machine.systemd.package}/bin/systemctl --user start wayland-proxy-virtwl >&2")
|
||||
'';
|
||||
}
|
||||
)
|
@ -1,25 +0,0 @@
|
||||
(import ../lib/container-test.nix) (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "zt-tcp-relay";
|
||||
|
||||
nodes.machine =
|
||||
{ self, ... }:
|
||||
{
|
||||
imports = [
|
||||
self.nixosModules.clanCore
|
||||
self.clanModules.zt-tcp-relay
|
||||
{
|
||||
clanCore.machineName = "machine";
|
||||
clanCore.clanDir = ./.;
|
||||
}
|
||||
];
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("zt-tcp-relay.service")
|
||||
out = machine.succeed("${pkgs.netcat}/bin/nc -z -v localhost 4443")
|
||||
print(out)
|
||||
'';
|
||||
}
|
||||
)
|
90
clanModules/borgbackup.nix
Normal file
90
clanModules/borgbackup.nix
Normal file
@ -0,0 +1,90 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.clan.borgbackup;
|
||||
in
|
||||
{
|
||||
options.clan.borgbackup = {
|
||||
enable = lib.mkEnableOption "backups with borgbackup";
|
||||
destinations = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = "the name of the backup job";
|
||||
};
|
||||
repo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "the borgbackup repository to backup to";
|
||||
};
|
||||
rsh = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ssh -i ${config.clanCore.secrets.borgbackup.secrets."borgbackup.ssh".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
||||
description = "the rsh to use for the backup";
|
||||
};
|
||||
|
||||
};
|
||||
}));
|
||||
description = ''
|
||||
destinations where the machine should be backuped to
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.borgbackup.jobs = lib.mapAttrs
|
||||
(_: dest: {
|
||||
paths = lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state));
|
||||
exclude = [
|
||||
"*.pyc"
|
||||
];
|
||||
repo = dest.repo;
|
||||
environment.BORG_RSH = dest.rsh;
|
||||
encryption.mode = "none";
|
||||
compression = "auto,zstd";
|
||||
startAt = "*-*-* 01:00:00";
|
||||
preHook = ''
|
||||
set -x
|
||||
'';
|
||||
|
||||
prune.keep = {
|
||||
within = "1d"; # Keep all archives from the last day
|
||||
daily = 7;
|
||||
weekly = 4;
|
||||
monthly = 0;
|
||||
};
|
||||
})
|
||||
cfg.destinations;
|
||||
|
||||
clanCore.secrets.borgbackup = {
|
||||
facts."borgbackup.ssh.pub" = { };
|
||||
secrets."borgbackup.ssh" = { };
|
||||
generator.path = [ pkgs.openssh pkgs.coreutils ];
|
||||
generator.script = ''
|
||||
ssh-keygen -t ed25519 -N "" -f "$secrets"/borgbackup.ssh
|
||||
mv "$secrets"/borgbackup.ssh.pub "$facts"/borgbackup.ssh.pub
|
||||
'';
|
||||
};
|
||||
|
||||
clanCore.backups.providers.borgbackup = {
|
||||
# TODO list needs to run locally or on the remote machine
|
||||
list = ''
|
||||
${lib.concatMapStringsSep "\n" (dest: ''
|
||||
# we need yes here to skip the changed url verification
|
||||
yes y | borg-job-${dest.name} list --json | jq -r '. + {"job-name": "${dest.name}"}'
|
||||
'') (lib.attrValues cfg.destinations)}
|
||||
'';
|
||||
create = ''
|
||||
${lib.concatMapStringsSep "\n" (dest: ''
|
||||
systemctl start borgbackup-job-${dest.name}
|
||||
'') (lib.attrValues cfg.destinations)}
|
||||
'';
|
||||
|
||||
restore = ''
|
||||
set -efu
|
||||
cd /
|
||||
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
||||
yes y | borg-job-"$JOB" extract --list --dry-run "$LOCATION"::"$ARCHIVE_ID" "''${FOLDER[@]}"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
Efficient, deduplicating backup program with optional compression and secure encryption.
|
||||
---
|
@ -1,131 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.clan.borgbackup;
|
||||
in
|
||||
{
|
||||
options.clan.borgbackup.destinations = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
|
||||
default = name;
|
||||
description = "the name of the backup job";
|
||||
};
|
||||
repo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "the borgbackup repository to backup to";
|
||||
};
|
||||
rsh = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ssh -i ${
|
||||
config.clanCore.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";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
description = ''
|
||||
destinations where the machine should be backuped to
|
||||
'';
|
||||
};
|
||||
|
||||
imports = [
|
||||
(lib.mkRemovedOptionModule [
|
||||
"clan"
|
||||
"borgbackup"
|
||||
"enable"
|
||||
] "Just define clan.borgbackup.destinations to enable it")
|
||||
];
|
||||
|
||||
config = lib.mkIf (cfg.destinations != { }) {
|
||||
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
|
||||
paths = lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state));
|
||||
exclude = [ "*.pyc" ];
|
||||
repo = dest.repo;
|
||||
environment.BORG_RSH = dest.rsh;
|
||||
compression = "auto,zstd";
|
||||
startAt = "*-*-* 01:00:00";
|
||||
persistentTimer = true;
|
||||
preHook = ''
|
||||
set -x
|
||||
'';
|
||||
|
||||
encryption = {
|
||||
mode = "repokey";
|
||||
passCommand = "cat ${config.clanCore.facts.services.borgbackup.secret."borgbackup.repokey".path}";
|
||||
};
|
||||
|
||||
prune.keep = {
|
||||
within = "1d"; # Keep all archives from the last day
|
||||
daily = 7;
|
||||
weekly = 4;
|
||||
monthly = 0;
|
||||
};
|
||||
}) cfg.destinations;
|
||||
|
||||
clanCore.facts.services.borgbackup = {
|
||||
public."borgbackup.ssh.pub" = { };
|
||||
secret."borgbackup.ssh" = { };
|
||||
secret."borgbackup.repokey" = { };
|
||||
generator.path = [
|
||||
pkgs.openssh
|
||||
pkgs.coreutils
|
||||
pkgs.xkcdpass
|
||||
];
|
||||
generator.script = ''
|
||||
ssh-keygen -t ed25519 -N "" -f "$secrets"/borgbackup.ssh
|
||||
mv "$secrets"/borgbackup.ssh.pub "$facts"/borgbackup.ssh.pub
|
||||
xkcdpass -n 4 -d - > "$secrets"/borgbackup.repokey
|
||||
'';
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "borgbackup-create" ''
|
||||
set -efu -o pipefail
|
||||
${lib.concatMapStringsSep "\n" (dest: ''
|
||||
systemctl start borgbackup-job-${dest.name}
|
||||
'') (lib.attrValues cfg.destinations)}
|
||||
'')
|
||||
(pkgs.writeShellScriptBin "borgbackup-list" ''
|
||||
set -efu
|
||||
(${
|
||||
lib.concatMapStringsSep "\n" (
|
||||
dest:
|
||||
# we need yes here to skip the changed url verification
|
||||
''yes y | borg-job-${dest.name} list --json | jq '[.archives[] | {"name": ("${dest.name}::${dest.repo}::" + .name)}]' ''
|
||||
) (lib.attrValues cfg.destinations)
|
||||
}) | ${pkgs.jq}/bin/jq -s 'add'
|
||||
'')
|
||||
(pkgs.writeShellScriptBin "borgbackup-restore" ''
|
||||
set -efux
|
||||
cd /
|
||||
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
||||
job_name=$(echo "$NAME" | ${pkgs.gawk}/bin/awk -F'::' '{print $1}')
|
||||
backup_name=''${NAME#"$job_name"::}
|
||||
if ! command -v borg-job-"$job_name" &> /dev/null; then
|
||||
echo "borg-job-$job_name not found: Backup name is invalid" >&2
|
||||
exit 1
|
||||
fi
|
||||
yes y | borg-job-"$job_name" extract --list "$backup_name" "''${FOLDER[@]}"
|
||||
'')
|
||||
];
|
||||
|
||||
clanCore.backups.providers.borgbackup = {
|
||||
list = "borgbackup-list";
|
||||
create = "borgbackup-create";
|
||||
restore = "borgbackup-restore";
|
||||
};
|
||||
};
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
{ config, pkgs, ... }: {
|
||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 25 ]; # smtp with other hosts
|
||||
environment.systemPackages = [ pkgs.deltachat-desktop ];
|
||||
|
||||
services.maddy =
|
||||
let
|
||||
domain = "${config.clanCore.machineName}.local";
|
||||
# FIXME move this to public setting
|
||||
meshname = config.clanCore.secrets.zerotier.facts.zerotier-meshname.value or null;
|
||||
domain = if meshname == null then "${config.clanCore.machineName}.local" else "${meshname}.vpn";
|
||||
in
|
||||
{
|
||||
enable = true;
|
||||
@ -135,7 +136,9 @@
|
||||
storage &local_mailboxes
|
||||
}
|
||||
'';
|
||||
ensureAccounts = [ "user@${domain}" ];
|
||||
ensureAccounts = [
|
||||
"user@${domain}"
|
||||
];
|
||||
ensureCredentials = {
|
||||
"user@${domain}".passwordFile = pkgs.writeText "dummy" "foobar";
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
Email-based instant messaging for Desktop.
|
||||
---
|
||||
|
||||
!!! warning "Under construction"
|
||||
|
||||
!!! info
|
||||
This module will automatically configure an email server on the machine for handling the e-mail messaging seamlessly.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] **Email-based**: Uses any email account as its backend.
|
||||
- [x] **End-to-End Encryption**: Supports Autocrypt to automatically encrypt messages.
|
||||
- [x] **No Phone Number Required**: Uses your email address instead of a phone number.
|
||||
- [x] **Cross-Platform**: Available on desktop and mobile platforms.
|
||||
- [x] **Automatic Server Setup**: Includes your own DeltaChat server for enhanced control and privacy.
|
||||
- [ ] **Bake a cake**: This module cannot cake a bake.
|
@ -1,2 +0,0 @@
|
||||
Automatically format a disk drive on clan installation
|
||||
---
|
@ -1,48 +0,0 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
options.clan.disk-layouts.singleDiskExt4 = {
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "/dev/disk/by-id/ata-Samsung_SSD_850_EVO_250GB_S21PNXAGB12345";
|
||||
};
|
||||
};
|
||||
config = {
|
||||
boot.loader.grub.efiSupport = lib.mkDefault true;
|
||||
boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true;
|
||||
disko.devices = {
|
||||
disk = {
|
||||
main = {
|
||||
type = "disk";
|
||||
device = config.clan.disk-layouts.singleDiskExt4.device;
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # for grub MBR
|
||||
priority = 1;
|
||||
};
|
||||
ESP = {
|
||||
size = "512M";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
};
|
||||
};
|
||||
root = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "ext4";
|
||||
mountpoint = "/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -1,20 +1,22 @@
|
||||
{ lib, ... }:
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
boot.loader.grub.efiSupport = lib.mkDefault true;
|
||||
boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true;
|
||||
disko.devices = {
|
||||
options.clan.diskLayouts.singleDiskExt4 = {
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "/dev/disk/by-id/ata-Samsung_SSD_850_EVO_250GB_S21PNXAGB12345";
|
||||
};
|
||||
};
|
||||
config.disko.devices = {
|
||||
disk = {
|
||||
main = {
|
||||
type = "disk";
|
||||
# Set the following in flake.nix for each maschine:
|
||||
# device = <uuid>;
|
||||
device = config.clan.diskLayouts.singleDiskExt4.device;
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # for grub MBR
|
||||
priority = 1;
|
||||
};
|
||||
ESP = {
|
||||
size = "512M";
|
||||
@ -39,3 +41,4 @@
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
A modern IRC server
|
||||
---
|
@ -1,14 +0,0 @@
|
||||
_: {
|
||||
services.ergochat = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
datastore = {
|
||||
autoupgrade = true;
|
||||
path = "/var/lib/ergo/ircd.db";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
clanCore.state.ergochat.folders = [ "/var/lib/ergo" ];
|
||||
}
|
@ -1,26 +1,14 @@
|
||||
{ ... }:
|
||||
{
|
||||
{ inputs, ... }: {
|
||||
flake.clanModules = {
|
||||
disk-layouts = {
|
||||
imports = [ ./disk-layouts ];
|
||||
diskLayouts = {
|
||||
imports = [
|
||||
./diskLayouts.nix
|
||||
inputs.disko.nixosModules.default
|
||||
];
|
||||
};
|
||||
borgbackup = ./borgbackup;
|
||||
deltachat = ./deltachat;
|
||||
ergochat = ./ergochat;
|
||||
localbackup = ./localbackup;
|
||||
localsend = ./localsend;
|
||||
matrix-synapse = ./matrix-synapse;
|
||||
moonlight = ./moonlight;
|
||||
root-password = ./root-password;
|
||||
sshd = ./sshd;
|
||||
sunshine = ./sunshine;
|
||||
static-hosts = ./static-hosts;
|
||||
syncthing = ./syncthing;
|
||||
thelounge = ./thelounge;
|
||||
trusted-nix-caches = ./trusted-nix-caches;
|
||||
user-password = ./user-password;
|
||||
xfce = ./xfce;
|
||||
zerotier-static-peers = ./zerotier-static-peers;
|
||||
zt-tcp-relay = ./zt-tcp-relay;
|
||||
deltachat = ./deltachat.nix;
|
||||
xfce = ./xfce.nix;
|
||||
borgbackup = ./borgbackup.nix;
|
||||
syncthing = ./syncthing.nix;
|
||||
};
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
Automatically backups current machine to local directory.
|
||||
---
|
@ -1,223 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.clan.localbackup;
|
||||
rsnapshotConfig = target: states: ''
|
||||
config_version 1.2
|
||||
snapshot_root ${target.directory}
|
||||
sync_first 1
|
||||
cmd_cp ${pkgs.coreutils}/bin/cp
|
||||
cmd_rm ${pkgs.coreutils}/bin/rm
|
||||
cmd_rsync ${pkgs.rsync}/bin/rsync
|
||||
cmd_ssh ${pkgs.openssh}/bin/ssh
|
||||
cmd_logger ${pkgs.inetutils}/bin/logger
|
||||
cmd_du ${pkgs.coreutils}/bin/du
|
||||
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) ''
|
||||
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
|
||||
set -efu -o pipefail
|
||||
${target.postBackupHook}
|
||||
''}
|
||||
''}
|
||||
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
||||
${lib.concatMapStringsSep "\n" (state: ''
|
||||
${lib.concatMapStringsSep "\n" (folder: ''
|
||||
backup ${folder} ${config.networking.hostName}/
|
||||
'') state.folders}
|
||||
'') states}
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.clan.localbackup = {
|
||||
targets = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
|
||||
default = name;
|
||||
description = "the name of the backup job";
|
||||
};
|
||||
directory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "the directory to backup";
|
||||
};
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "mountpoint of the directory to backup. If set, the directory will be mounted before the backup and unmounted afterwards";
|
||||
};
|
||||
preMountHook = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = "Shell commands to run before the directory is mounted";
|
||||
};
|
||||
postMountHook = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = "Shell commands to run after the directory is mounted";
|
||||
};
|
||||
preUnmountHook = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = "Shell commands to run before the directory is unmounted";
|
||||
};
|
||||
postUnmountHook = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = "Shell commands to run after the directory is unmounted";
|
||||
};
|
||||
preBackupHook = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = "Shell commands to run before the backup";
|
||||
};
|
||||
postBackupHook = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = "Shell commands to run after the backup";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
description = "List of directories where backups are stored";
|
||||
};
|
||||
|
||||
snapshots = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 20;
|
||||
description = "Number of snapshots to keep";
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
mountHook = target: ''
|
||||
if [[ -x /run/current-system/sw/bin/localbackup-mount-${target.name} ]]; then
|
||||
/run/current-system/sw/bin/localbackup-mount-${target.name}
|
||||
fi
|
||||
if [[ -x /run/current-system/sw/bin/localbackup-unmount-${target.name} ]]; then
|
||||
trap "/run/current-system/sw/bin/localbackup-unmount-${target.name}" EXIT
|
||||
fi
|
||||
'';
|
||||
in
|
||||
lib.mkIf (cfg.targets != { }) {
|
||||
environment.systemPackages =
|
||||
[
|
||||
(pkgs.writeShellScriptBin "localbackup-create" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.rsnapshot
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
]
|
||||
}
|
||||
${lib.concatMapStringsSep "\n" (target: ''
|
||||
(
|
||||
${mountHook target}
|
||||
echo "Creating backup '${target.name}'"
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" sync
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" snapshot
|
||||
)
|
||||
'') (builtins.attrValues cfg.targets)}
|
||||
'')
|
||||
(pkgs.writeShellScriptBin "localbackup-list" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.jq
|
||||
pkgs.findutils
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
]
|
||||
}
|
||||
(${
|
||||
lib.concatMapStringsSep "\n" (target: ''
|
||||
(
|
||||
${mountHook target}
|
||||
find ${lib.escapeShellArg target.directory} -mindepth 1 -maxdepth 1 -name "snapshot.*" -print0 -type d \
|
||||
| jq -Rs 'split("\u0000") | .[] | select(. != "") | { "name": ("${target.name}::" + .)}'
|
||||
)
|
||||
'') (builtins.attrValues cfg.targets)
|
||||
}) | jq -s .
|
||||
'')
|
||||
(pkgs.writeShellScriptBin "localbackup-restore" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.rsync
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
pkgs.gawk
|
||||
]
|
||||
}
|
||||
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
||||
backupname=''${NAME#$name::}
|
||||
|
||||
if command -v localbackup-mount-$name; then
|
||||
localbackup-mount-$name
|
||||
fi
|
||||
if command -v localbackup-unmount-$name; then
|
||||
trap "localbackup-unmount-$name" EXIT
|
||||
fi
|
||||
|
||||
if [[ ! -d $backupname ]]; then
|
||||
echo "No backup found $backupname"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
||||
for folder in "''${FOLDER[@]}"; do
|
||||
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
||||
done
|
||||
'')
|
||||
]
|
||||
++ (lib.mapAttrsToList (
|
||||
name: target:
|
||||
pkgs.writeShellScriptBin ("localbackup-mount-" + name) ''
|
||||
set -efu -o pipefail
|
||||
${lib.optionalString (target.preMountHook != null) target.preMountHook}
|
||||
${lib.optionalString (target.mountpoint != null) ''
|
||||
if ! ${pkgs.util-linux}/bin/mountpoint -q ${lib.escapeShellArg target.mountpoint}; then
|
||||
${pkgs.util-linux}/bin/mount -o X-mount.mkdir ${lib.escapeShellArg target.mountpoint}
|
||||
fi
|
||||
''}
|
||||
${lib.optionalString (target.postMountHook != null) target.postMountHook}
|
||||
''
|
||||
) cfg.targets)
|
||||
++ lib.mapAttrsToList (
|
||||
name: target:
|
||||
pkgs.writeShellScriptBin ("localbackup-unmount-" + name) ''
|
||||
set -efu -o pipefail
|
||||
${lib.optionalString (target.preUnmountHook != null) target.preUnmountHook}
|
||||
${lib.optionalString (
|
||||
target.mountpoint != null
|
||||
) "${pkgs.util-linux}/bin/umount ${lib.escapeShellArg target.mountpoint}"}
|
||||
${lib.optionalString (target.postUnmountHook != null) target.postUnmountHook}
|
||||
''
|
||||
) cfg.targets;
|
||||
|
||||
clanCore.backups.providers.localbackup = {
|
||||
# TODO list needs to run locally or on the remote machine
|
||||
list = "localbackup-list";
|
||||
create = "localbackup-create";
|
||||
restore = "localbackup-restore";
|
||||
};
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
Securely sharing files and messages over a local network without internet connectivity.
|
||||
---
|
@ -1,38 +0,0 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
# Integration can be improved, if the following issues get implemented:
|
||||
# - cli frontend: https://github.com/localsend/localsend/issues/11
|
||||
# - ipv6 support: https://github.com/localsend/localsend/issues/549
|
||||
options.clan.localsend = {
|
||||
enable = lib.mkEnableOption "enable the localsend module";
|
||||
defaultLocation = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "The default download location";
|
||||
};
|
||||
package = lib.mkPackageOption pkgs "localsend" { };
|
||||
};
|
||||
|
||||
config = lib.mkIf config.clan.localsend.enable {
|
||||
clanCore.state.localsend.folders = [
|
||||
"/var/localsend"
|
||||
config.clan.localsend.defaultLocation
|
||||
];
|
||||
environment.systemPackages = [ config.clan.localsend.package ];
|
||||
|
||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 53317 ];
|
||||
networking.firewall.interfaces."zt+".allowedUDPPorts = [ 53317 ];
|
||||
|
||||
#TODO: This is currently needed because there is no ipv6 multicasting support yet
|
||||
#
|
||||
systemd.network.networks."09-zerotier" = {
|
||||
networkConfig = {
|
||||
Address = "192.168.56.2/24";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
A federated messaging server with end-to-end encryption.
|
||||
---
|
@ -1,127 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.clan.matrix-synapse;
|
||||
in
|
||||
{
|
||||
options.clan.matrix-synapse = {
|
||||
enable = lib.mkEnableOption "Enable matrix-synapse";
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "The domain name of the matrix server";
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.matrix-synapse = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server_name = cfg.domain;
|
||||
database = {
|
||||
args.user = "matrix-synapse";
|
||||
args.database = "matrix-synapse";
|
||||
name = "psycopg2";
|
||||
};
|
||||
turn_uris = [
|
||||
"turn:turn.matrix.org?transport=udp"
|
||||
"turn:turn.matrix.org?transport=tcp"
|
||||
];
|
||||
listeners = [
|
||||
{
|
||||
port = 8008;
|
||||
bind_addresses = [ "::1" ];
|
||||
type = "http";
|
||||
tls = false;
|
||||
x_forwarded = true;
|
||||
resources = [
|
||||
{
|
||||
names = [ "client" ];
|
||||
compress = true;
|
||||
}
|
||||
{
|
||||
names = [ "federation" ];
|
||||
compress = false;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
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
|
||||
''}"
|
||||
];
|
||||
|
||||
clanCore.facts.services."matrix-synapse" = {
|
||||
secret."synapse-registration_shared_secret" = { };
|
||||
generator.path = with pkgs; [
|
||||
coreutils
|
||||
pwgen
|
||||
];
|
||||
generator.script = ''
|
||||
echo "registration_shared_secret: $(pwgen -s 32 1)" > "$secrets"/synapse-registration_shared_secret
|
||||
'';
|
||||
};
|
||||
|
||||
services.postgresql.enable = true;
|
||||
# we need to use both ensusureDatabases and initialScript, because the former runs everytime but with the wrong collation
|
||||
services.postgresql = {
|
||||
ensureDatabases = [ "matrix-synapse" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "matrix-synapse";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
initialScript = pkgs.writeText "synapse-init.sql" ''
|
||||
CREATE DATABASE "matrix-synapse"
|
||||
TEMPLATE template0
|
||||
LC_COLLATE = "C"
|
||||
LC_CTYPE = "C";
|
||||
'';
|
||||
};
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
${cfg.domain} = {
|
||||
locations."= /.well-known/matrix/server".extraConfig = ''
|
||||
add_header Content-Type application/json;
|
||||
return 200 '${builtins.toJSON { "m.server" = "matrix.${cfg.domain}:443"; }}';
|
||||
'';
|
||||
locations."= /.well-known/matrix/client".extraConfig = ''
|
||||
add_header Content-Type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
return 200 '${
|
||||
builtins.toJSON {
|
||||
"m.homeserver" = {
|
||||
"base_url" = "https://matrix.${cfg.domain}";
|
||||
};
|
||||
"m.identity_server" = {
|
||||
"base_url" = "https://vector.im";
|
||||
};
|
||||
}
|
||||
}';
|
||||
'';
|
||||
};
|
||||
"matrix.${cfg.domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/_matrix" = {
|
||||
proxyPass = "http://localhost:8008";
|
||||
};
|
||||
locations."/test".extraConfig = ''
|
||||
return 200 "Hello, world!";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
A desktop streaming client optimized for remote gaming and synchronized movie viewing.
|
||||
---
|
@ -1,86 +0,0 @@
|
||||
{ pkgs, config, ... }:
|
||||
let
|
||||
ms-accept = pkgs.callPackage ../pkgs/moonlight-sunshine-accept { };
|
||||
defaultPort = 48011;
|
||||
in
|
||||
{
|
||||
hardware.opengl.enable = true;
|
||||
environment.systemPackages = [
|
||||
pkgs.moonlight-qt
|
||||
ms-accept
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/var/lib/moonlight' 0770 'user' 'users' - -"
|
||||
"C '/var/lib/moonlight/moonlight.cert' 0644 'user' 'users' - ${
|
||||
config.clanCore.facts.services.moonlight.secret."moonlight.cert".path or ""
|
||||
}"
|
||||
"C '/var/lib/moonlight/moonlight.key' 0644 'user' 'users' - ${
|
||||
config.clanCore.facts.services.moonlight.secret."moonlight.key".path or ""
|
||||
}"
|
||||
];
|
||||
|
||||
systemd.user.services.init-moonlight = {
|
||||
enable = false;
|
||||
description = "Initializes moonlight";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
script = ''
|
||||
${ms-accept}/bin/moonlight-sunshine-accept moonlight init-config --key /var/lib/moonlight/moonlight.key --cert /var/lib/moonlight/moonlight.cert
|
||||
'';
|
||||
serviceConfig = {
|
||||
user = "user";
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = "/home/user/";
|
||||
RunTimeDirectory = "moonlight";
|
||||
TimeoutSec = "infinity";
|
||||
Restart = "on-failure";
|
||||
RemainAfterExit = true;
|
||||
ReadOnlyPaths = [
|
||||
"/var/lib/moonlight/moonlight.key"
|
||||
"/var/lib/moonlight/moonlight.cert"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.services.moonlight-join = {
|
||||
description = "Join sunshine hosts";
|
||||
script = ''${ms-accept}/bin/moonlight-sunshine-accept moonlight join --port ${builtins.toString defaultPort} --cert '${
|
||||
config.clanCore.facts.services.moonlight.public."moonlight.cert".value or ""
|
||||
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutSec = "infinity";
|
||||
Restart = "on-failure";
|
||||
ReadOnlyPaths = [
|
||||
"/var/lib/moonlight/moonlight.key"
|
||||
"/var/lib/moonlight/moonlight.cert"
|
||||
];
|
||||
};
|
||||
};
|
||||
systemd.user.timers.moonlight-join = {
|
||||
description = "Join sunshine hosts";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnUnitActiveSec = "5min";
|
||||
OnBootSec = "0min";
|
||||
Persistent = true;
|
||||
Unit = "moonlight-join.service";
|
||||
};
|
||||
};
|
||||
|
||||
clanCore.facts.services.moonlight = {
|
||||
secret."moonlight.key" = { };
|
||||
secret."moonlight.cert" = { };
|
||||
public."moonlight.cert" = { };
|
||||
generator.path = [
|
||||
pkgs.coreutils
|
||||
ms-accept
|
||||
];
|
||||
generator.script = ''
|
||||
moonlight-sunshine-accept moonlight init
|
||||
mv credentials/cakey.pem "$secrets"/moonlight.key
|
||||
cp credentials/cacert.pem "$secrets"/moonlight.cert
|
||||
mv credentials/cacert.pem "$facts"/moonlight.cert
|
||||
'';
|
||||
};
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
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:
|
||||
|
||||
```bash
|
||||
clan secrets get {machine_name}-password
|
||||
```
|
||||
|
||||
|
||||
See also: [Facts / Secrets](../../getting-started/secrets.md)
|
@ -1,20 +0,0 @@
|
||||
{ pkgs, config, ... }:
|
||||
{
|
||||
users.mutableUsers = false;
|
||||
users.users.root.hashedPasswordFile =
|
||||
config.clanCore.facts.services.root-password.secret.password-hash.path;
|
||||
sops.secrets."${config.clanCore.machineName}-password-hash".neededForUsers = true;
|
||||
clanCore.facts.services.root-password = {
|
||||
secret.password = { };
|
||||
secret.password-hash = { };
|
||||
generator.path = with pkgs; [
|
||||
coreutils
|
||||
xkcdpass
|
||||
mkpasswd
|
||||
];
|
||||
generator.script = ''
|
||||
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/password
|
||||
cat $secrets/password | mkpasswd -s -m sha-512 > $secrets/password-hash
|
||||
'';
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
Enables secure remote access to the machine over ssh
|
||||
---
|
@ -1,25 +0,0 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
services.openssh.enable = true;
|
||||
services.openssh.settings.PasswordAuthentication = false;
|
||||
|
||||
services.openssh.hostKeys = [
|
||||
{
|
||||
path = config.clanCore.facts.services.openssh.secret."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
];
|
||||
|
||||
clanCore.facts.services.openssh = {
|
||||
secret."ssh.id_ed25519" = { };
|
||||
public."ssh.id_ed25519.pub" = { };
|
||||
generator.path = [
|
||||
pkgs.coreutils
|
||||
pkgs.openssh
|
||||
];
|
||||
generator.script = ''
|
||||
ssh-keygen -t ed25519 -N "" -f $secrets/ssh.id_ed25519
|
||||
mv $secrets/ssh.id_ed25519.pub $facts/ssh.id_ed25519.pub
|
||||
'';
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
Statically configure the host names of machines based on their respective zerotier-ip.
|
||||
---
|
@ -1,44 +0,0 @@
|
||||
{ lib, config, ... }:
|
||||
{
|
||||
options.clan.static-hosts = {
|
||||
excludeHosts = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default =
|
||||
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clanCore.machineName ];
|
||||
description = "Hosts that should be excluded";
|
||||
};
|
||||
topLevelDomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Top level domain to reach hosts";
|
||||
};
|
||||
};
|
||||
|
||||
config.networking.hosts =
|
||||
let
|
||||
clanDir = config.clanCore.clanDir;
|
||||
machineDir = clanDir + "/machines/";
|
||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||
machines = builtins.readDir machineDir;
|
||||
filteredMachines = lib.filterAttrs (
|
||||
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
||||
) machines;
|
||||
in
|
||||
lib.filterAttrs (_: value: value != null) (
|
||||
lib.mapAttrs' (
|
||||
machine: _:
|
||||
let
|
||||
path = zerotierIpMachinePath machine;
|
||||
in
|
||||
if builtins.pathExists path then
|
||||
lib.nameValuePair (builtins.readFile path) (
|
||||
if (config.clan.static-hosts.topLevelDomain == "") then
|
||||
[ machine ]
|
||||
else
|
||||
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
|
||||
)
|
||||
else
|
||||
null
|
||||
) filteredMachines
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
A desktop streaming server optimized for remote gaming and synchronized movie viewing.
|
||||
---
|
@ -1,207 +0,0 @@
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
ms-accept = pkgs.callPackage ../pkgs/moonlight-sunshine-accept { };
|
||||
sunshineConfiguration = pkgs.writeText "sunshine.conf" ''
|
||||
address_family = both
|
||||
channels = 5
|
||||
pkey = /var/lib/sunshine/sunshine.key
|
||||
cert = /var/lib/sunshine/sunshine.cert
|
||||
file_state = /var/lib/sunshine/state.json
|
||||
credentials_file = /var/lib/sunshine/credentials.json
|
||||
'';
|
||||
listenPort = 48011;
|
||||
in
|
||||
{
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [
|
||||
47984
|
||||
47989
|
||||
47990
|
||||
48010
|
||||
48011
|
||||
];
|
||||
|
||||
allowedUDPPorts = [
|
||||
47998
|
||||
47999
|
||||
48000
|
||||
48002
|
||||
48010
|
||||
];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPortRanges = [
|
||||
{
|
||||
from = 47984;
|
||||
to = 48010;
|
||||
}
|
||||
];
|
||||
networking.firewall.allowedUDPPortRanges = [
|
||||
{
|
||||
from = 47998;
|
||||
to = 48010;
|
||||
}
|
||||
];
|
||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [
|
||||
47984
|
||||
47989
|
||||
47990
|
||||
48010
|
||||
listenPort
|
||||
];
|
||||
networking.firewall.interfaces."zt+".allowedUDPPortRanges = [
|
||||
{
|
||||
from = 47998;
|
||||
to = 48010;
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [
|
||||
ms-accept
|
||||
pkgs.sunshine
|
||||
pkgs.avahi
|
||||
# Convenience script, until we find a better UX
|
||||
(pkgs.writers.writeDashBin "sun" ''
|
||||
${pkgs.sunshine}/bin/sunshine -0 ${sunshineConfiguration} "$@"
|
||||
'')
|
||||
# Create a dummy account, for easier setup,
|
||||
# don't use this account in actual production yet.
|
||||
(pkgs.writers.writeDashBin "init-sun" ''
|
||||
${pkgs.sunshine}/bin/sunshine \
|
||||
--creds "sunshine" "sunshine"
|
||||
'')
|
||||
];
|
||||
|
||||
# Required to simulate input
|
||||
boot.kernelModules = [ "uinput" ];
|
||||
|
||||
services.udev.extraRules = ''
|
||||
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"
|
||||
'';
|
||||
|
||||
security = {
|
||||
rtkit.enable = true;
|
||||
wrappers.sunshine = {
|
||||
owner = "root";
|
||||
group = "root";
|
||||
capabilities = "cap_sys_admin+p";
|
||||
source = "${pkgs.sunshine}/bin/sunshine";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/var/lib/sunshine' 0770 'user' 'users' - -"
|
||||
"C '/var/lib/sunshine/sunshine.cert' 0644 'user' 'users' - ${
|
||||
config.clanCore.facts.services.sunshine.secret."sunshine.cert".path or ""
|
||||
}"
|
||||
"C '/var/lib/sunshine/sunshine.key' 0644 'user' 'users' - ${
|
||||
config.clanCore.facts.services.sunshine.secret."sunshine.key".path or ""
|
||||
}"
|
||||
];
|
||||
|
||||
hardware.opengl.enable = true;
|
||||
|
||||
systemd.user.services.sunshine = {
|
||||
enable = true;
|
||||
description = "Sunshine self-hosted game stream host for Moonlight";
|
||||
startLimitBurst = 5;
|
||||
startLimitIntervalSec = 500;
|
||||
script = "/run/current-system/sw/bin/env /run/wrappers/bin/sunshine ${sunshineConfiguration}";
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
ReadWritePaths = [ "/var/lib/sunshine" ];
|
||||
ReadOnlyPaths = [
|
||||
(config.clanCore.facts.services.sunshine.secret."sunshine.key".path or "")
|
||||
(config.clanCore.facts.services.sunshine.secret."sunshine.cert".path or "")
|
||||
];
|
||||
};
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
partOf = [ "graphical-session.target" ];
|
||||
wants = [ "graphical-session.target" ];
|
||||
after = [
|
||||
"sunshine-init-state.service"
|
||||
"sunshine-init-credentials.service"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.user.services.sunshine-init-state = {
|
||||
enable = true;
|
||||
description = "Sunshine self-hosted game stream host for Moonlight";
|
||||
startLimitBurst = 5;
|
||||
startLimitIntervalSec = 500;
|
||||
script = ''
|
||||
${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${
|
||||
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
|
||||
} --state-file /var/lib/sunshine/state.json
|
||||
'';
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
Type = "oneshot";
|
||||
ReadWritePaths = [ "/var/lib/sunshine" ];
|
||||
};
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
};
|
||||
|
||||
systemd.user.services.sunshine-init-credentials = {
|
||||
enable = true;
|
||||
description = "Sunshine self-hosted game stream host for Moonlight";
|
||||
startLimitBurst = 5;
|
||||
startLimitIntervalSec = 500;
|
||||
script = ''
|
||||
${lib.getExe pkgs.sunshine} ${sunshineConfiguration} --creds sunshine sunshine
|
||||
'';
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
Type = "oneshot";
|
||||
ReadWritePaths = [ "/var/lib/sunshine" ];
|
||||
};
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
};
|
||||
|
||||
systemd.user.services.sunshine-listener = {
|
||||
enable = true;
|
||||
description = "Sunshine self-hosted game stream host for Moonlight";
|
||||
startLimitBurst = 5;
|
||||
startLimitIntervalSec = 500;
|
||||
script = ''
|
||||
${ms-accept}/bin/moonlight-sunshine-accept sunshine listen --port ${builtins.toString listenPort} --uuid ${
|
||||
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
|
||||
} --state /var/lib/sunshine/state.json --cert '${
|
||||
config.clanCore.facts.services.sunshine.public."sunshine.cert".value or null
|
||||
}'
|
||||
'';
|
||||
serviceConfig = {
|
||||
# );
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
ReadWritePaths = [ "/var/lib/sunshine" ];
|
||||
};
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
};
|
||||
|
||||
clanCore.facts.services.ergochat = {
|
||||
secret."sunshine.key" = { };
|
||||
secret."sunshine.cert" = { };
|
||||
public."sunshine-uuid" = { };
|
||||
public."sunshine.cert" = { };
|
||||
generator.path = [
|
||||
pkgs.coreutils
|
||||
ms-accept
|
||||
];
|
||||
generator.script = ''
|
||||
moonlight-sunshine-accept sunshine init
|
||||
mv credentials/cakey.pem "$secrets"/sunshine.key
|
||||
cp credentials/cacert.pem "$secrets"/sunshine.cert
|
||||
mv credentials/cacert.pem "$facts"/sunshine.cert
|
||||
mv uuid "$facts"/sunshine-uuid
|
||||
'';
|
||||
};
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
{
|
||||
options.clan.syncthing = {
|
||||
id = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
example = "BABNJY4-G2ICDLF-QQEG7DD-N3OBNGF-BCCOFK6-MV3K7QJ-2WUZHXS-7DTW4AS";
|
||||
default = config.clanCore.facts.services.syncthing.public."syncthing.pub".value or null;
|
||||
defaultText = "config.clanCore.facts.services.syncthing.public.\"syncthing.pub\".value";
|
||||
default = config.clanCore.secrets.syncthing.facts."syncthing.pub".value or null;
|
||||
};
|
||||
introducer = lib.mkOption {
|
||||
description = ''
|
||||
@ -34,10 +32,6 @@
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [
|
||||
"folder1"
|
||||
"folder2"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@ -59,37 +53,32 @@
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.all (
|
||||
attr: builtins.hasAttr attr config.services.syncthing.settings.folders
|
||||
) config.clan.syncthing.autoShares;
|
||||
assertion =
|
||||
lib.all (attr: builtins.hasAttr attr config.services.syncthing.settings.folders)
|
||||
config.clan.syncthing.autoShares;
|
||||
message = ''
|
||||
Syncthing: If you want to AutoShare a folder, you need to have it configured on the sharing device.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# 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";
|
||||
|
||||
overrideFolders = lib.mkDefault (
|
||||
if (config.clan.syncthing.introducer == null) then true else false
|
||||
);
|
||||
overrideDevices = lib.mkDefault (
|
||||
if (config.clan.syncthing.introducer == null) then true else false
|
||||
);
|
||||
overrideFolders = true;
|
||||
overrideDevices = true;
|
||||
|
||||
dataDir = lib.mkDefault "/home/user/";
|
||||
|
||||
group = "syncthing";
|
||||
|
||||
key = lib.mkDefault config.clan.secrets.syncthing.secrets."syncthing.key".path or null;
|
||||
cert = lib.mkDefault config.clan.secrets.syncthing.secrets."syncthing.cert".path or null;
|
||||
key =
|
||||
lib.mkDefault
|
||||
config.clanCore.secrets.syncthing.secrets."syncthing.key".path or null;
|
||||
cert =
|
||||
lib.mkDefault
|
||||
config.clanCore.secrets.syncthing.secrets."syncthing.cert".path or null;
|
||||
|
||||
settings = {
|
||||
options = {
|
||||
@ -119,7 +108,7 @@
|
||||
getPendingDevices = "/rest/cluster/pending/devices";
|
||||
postNewDevice = "/rest/config/devices";
|
||||
SharedFolderById = "/rest/config/folders/";
|
||||
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
|
||||
apiKey = config.clanCore.secrets.syncthing.secrets."syncthing.api".path or null;
|
||||
in
|
||||
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||
description = "Syncthing auto accept devices";
|
||||
@ -131,24 +120,38 @@
|
||||
set -x
|
||||
# query pending deviceID's
|
||||
APIKEY=$(cat ${apiKey})
|
||||
PENDING=$(${lib.getExe pkgs.curl} -X GET -H "X-API-Key: $APIKEY" ${baseAddress}${getPendingDevices})
|
||||
PENDING=$(${
|
||||
lib.getExe pkgs.curl
|
||||
} -X GET -H "X-API-Key: $APIKEY" ${baseAddress}${getPendingDevices})
|
||||
PENDING=$(echo $PENDING | ${lib.getExe pkgs.jq} keys[])
|
||||
|
||||
# accept pending deviceID's
|
||||
for ID in $PENDING;do
|
||||
${lib.getExe pkgs.curl} -X POST -d "{\"deviceId\": $ID}" -H "Content-Type: application/json" -H "X-API-Key: $APIKEY" ${baseAddress}${postNewDevice}
|
||||
${
|
||||
lib.getExe pkgs.curl
|
||||
} -X POST -d "{\"deviceId\": $ID}" -H "Content-Type: application/json" -H "X-API-Key: $APIKEY" ${baseAddress}${postNewDevice}
|
||||
|
||||
# get all shared folders by their ID
|
||||
for folder in ${builtins.toString config.clan.syncthing.autoShares}; do
|
||||
SHARED_IDS=$(${lib.getExe pkgs.curl} -X GET -H "X-API-Key: $APIKEY" ${baseAddress}${SharedFolderById}"$folder" | ${lib.getExe pkgs.jq} ."devices")
|
||||
PATCHED_IDS=$(echo $SHARED_IDS | ${lib.getExe pkgs.jq} ".+= [{\"deviceID\": $ID, \"introducedBy\": \"\", \"encryptionPassword\": \"\"}]")
|
||||
${lib.getExe pkgs.curl} -X PATCH -d "{\"devices\": $PATCHED_IDS}" -H "X-API-Key: $APIKEY" ${baseAddress}${SharedFolderById}"$folder"
|
||||
SHARED_IDS=$(${
|
||||
lib.getExe pkgs.curl
|
||||
} -X GET -H "X-API-Key: $APIKEY" ${baseAddress}${SharedFolderById}"$folder" | ${
|
||||
lib.getExe pkgs.jq
|
||||
} ."devices")
|
||||
PATCHED_IDS=$(echo $SHARED_IDS | ${
|
||||
lib.getExe pkgs.jq
|
||||
} ".+= [{\"deviceID\": $ID, \"introducedBy\": \"\", \"encryptionPassword\": \"\"}]")
|
||||
${
|
||||
lib.getExe pkgs.curl
|
||||
} -X PATCH -d "{\"devices\": $PATCHED_IDS}" -H "X-API-Key: $APIKEY" ${baseAddress}${SharedFolderById}"$folder"
|
||||
done
|
||||
done
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.syncthing-auto-accept = lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||
systemd.timers.syncthing-auto-accept =
|
||||
lib.mkIf config.clan.syncthing.autoAcceptDevices
|
||||
{
|
||||
description = "Syncthing Auto Accept";
|
||||
|
||||
wantedBy = [ "syncthing-auto-accept.service" ];
|
||||
@ -161,7 +164,7 @@
|
||||
|
||||
systemd.services.syncthing-init-api-key =
|
||||
let
|
||||
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
|
||||
apiKey = config.clanCore.secrets.syncthing.secrets."syncthing.api".path or null;
|
||||
in
|
||||
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||
description = "Set the api key";
|
||||
@ -172,7 +175,9 @@
|
||||
set -efu pipefail
|
||||
|
||||
APIKEY=$(cat ${apiKey})
|
||||
${lib.getExe pkgs.gnused} -i "s/<apikey>.*<\/apikey>/<apikey>$APIKEY<\/apikey>/" /var/lib/syncthing/config.xml
|
||||
${
|
||||
lib.getExe pkgs.gnused
|
||||
} -i "s/<apikey>.*<\/apikey>/<apikey>$APIKEY<\/apikey>/" /var/lib/syncthing/config.xml
|
||||
# sudo systemctl restart syncthing.service
|
||||
systemctl restart syncthing.service
|
||||
'';
|
||||
@ -183,22 +188,17 @@
|
||||
};
|
||||
};
|
||||
|
||||
clanCore.facts.services.syncthing = {
|
||||
secret."syncthing.key" = { };
|
||||
secret."syncthing.cert" = { };
|
||||
secret."syncthing.api" = { };
|
||||
public."syncthing.pub" = { };
|
||||
generator.path = [
|
||||
pkgs.coreutils
|
||||
pkgs.gnugrep
|
||||
pkgs.syncthing
|
||||
];
|
||||
clanCore.secrets.syncthing = {
|
||||
secrets."syncthing.key" = { };
|
||||
secrets."syncthing.cert" = { };
|
||||
secrets."syncthing.api" = { };
|
||||
facts."syncthing.pub" = { };
|
||||
generator.script = ''
|
||||
syncthing generate --config "$secrets"
|
||||
${pkgs.syncthing}/bin/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
|
||||
cat "$secrets"/config.xml | grep -oP '<apikey>\K[^<]+' | uniq > "$secrets"/syncthing.api
|
||||
cat "$secrets"/config.xml | ${pkgs.gnugrep}/bin/grep -oP '(?<=<device id=")[^"]+' | uniq > "$facts"/syncthing.pub
|
||||
cat "$secrets"/config.xml | ${pkgs.gnugrep}/bin/grep -oP '<apikey>\K[^<]+' | uniq > "$secrets"/syncthing.api
|
||||
'';
|
||||
};
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
A secure, file synchronization app for devices over networks, offering a private alternative to cloud services.
|
||||
---
|
||||
## Usage
|
||||
|
||||
We recommend configuring this module as an sync-service through the provided options. Although it provides a Web GUI through which more usage scenarios are supported.
|
||||
|
||||
## Features
|
||||
|
||||
- **Private and Secure**: Syncthing uses TLS encryption to secure data transfer between devices, ensuring that only the intended devices can read your data.
|
||||
- **Decentralized**: No central server is involved in the data transfer. Each device communicates directly with others.
|
||||
- **Open Source**: The source code is openly available for audit and contribution, fostering trust and continuous improvement.
|
||||
- **Cross-Platform**: Syncthing supports multiple platforms including Windows, macOS, Linux, BSD, and Android.
|
||||
- **Real-time Synchronization**: Changes made to files are synchronized in real-time across all connected devices.
|
||||
- **Web GUI**: It includes a user-friendly web interface for managing devices and configurations. (`127.0.0.1:8384`)
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Share Folders**: Select folders to share with connected devices and configure permissions and synchronization parameters.
|
||||
|
||||
!!! info
|
||||
Clan automatically discovers other devices. Automatic discovery requires one machine to be an [introducer](#clan.syncthing.introducer)
|
||||
|
||||
If that is not the case you can add the other device by its Device ID manually.
|
||||
You can find and share Device IDs under the "Add Device" button in the Web GUI. (`127.0.0.1:8384`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Sync Conflicts**: Resolve synchronization conflicts manually by reviewing file versions and modification times in the Web GUI (`127.0.0.1:8384`).
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation**: Extensive documentation is available on the [Syncthing website](https://docs.syncthing.net/).
|
@ -1,2 +0,0 @@
|
||||
Modern web IRC client
|
||||
---
|
@ -1,15 +0,0 @@
|
||||
_: {
|
||||
services.thelounge = {
|
||||
enable = true;
|
||||
public = true;
|
||||
extraConfig = {
|
||||
prefetch = true;
|
||||
defaults = {
|
||||
port = 6667;
|
||||
tls = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
clanCore.state.thelounde.folders = [ "/var/lib/thelounge" ];
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
This module sets the `clan.lol` and `nix-community` cache up as a trusted cache.
|
||||
----
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
nix.settings.trusted-substituters = [
|
||||
"https://cache.clan.lol"
|
||||
"https://nix-community.cachix.org"
|
||||
];
|
||||
nix.settings.trusted-public-keys = [
|
||||
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
||||
"cache.clan.lol-1:3KztgSAB5R1M+Dz7vzkBGzXdodizbgLXGXKXlcQLA28="
|
||||
];
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
Automatically generates and configures a password for the specified user account.
|
||||
---
|
||||
|
||||
If setting the option prompt to true, the user will be prompted to type in their desired password.
|
||||
|
||||
!!! Note
|
||||
This module will set `mutableUsers` to `false`, meaning you can not manage user passwords through `passwd` anymore.
|
||||
|
||||
|
||||
After the system was installed/deployed the following command can be used to display the user-password:
|
||||
|
||||
```bash
|
||||
clan secrets get {machine_name}-user-password
|
||||
```
|
||||
|
||||
See also: [Facts / Secrets](../../getting-started/secrets.md)
|
||||
|
||||
To regenerate the password, delete the password files in the clan directory and redeploy the machine.
|
@ -1,49 +0,0 @@
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
options.clan.user-password = {
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "alice";
|
||||
description = "The user the password should be generated for.";
|
||||
};
|
||||
prompt = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = "Whether the user should be prompted.";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
users.mutableUsers = false;
|
||||
users.users.${config.clan.user-password.user}.hashedPasswordFile =
|
||||
config.clanCore.facts.services.user-password.secret.user-password-hash.path;
|
||||
sops.secrets."${config.clanCore.machineName}-user-password-hash".neededForUsers = true;
|
||||
clanCore.facts.services.user-password = {
|
||||
secret.user-password = { };
|
||||
secret.user-password-hash = { };
|
||||
generator.prompt = (
|
||||
lib.mkIf config.clan.user-password.prompt "Set the password for your $user: ${config.clan.user-password.user}.
|
||||
You can autogenerate a password, if you leave this prompt blank."
|
||||
);
|
||||
generator.path = with pkgs; [
|
||||
coreutils
|
||||
xkcdpass
|
||||
mkpasswd
|
||||
];
|
||||
generator.script = ''
|
||||
if [[ -n $prompt_value ]]; then
|
||||
echo $prompt_value > $secrets/user-password
|
||||
else
|
||||
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/user-password
|
||||
fi
|
||||
cat $secrets/user-password | mkpasswd -s -m sha-512 > $secrets/user-password-hash
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
A lightweight desktop manager
|
||||
---
|
@ -1,5 +0,0 @@
|
||||
Statically configure the `zerotier` peers of a clan network.
|
||||
---
|
||||
Statically configure the `zerotier` peers of a clan network.
|
||||
|
||||
Requires a machine, that is the zerotier controller configured in the network.
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
clanDir = config.clanCore.clanDir;
|
||||
machineDir = clanDir + "/machines/";
|
||||
machinesFileSet = builtins.readDir machineDir;
|
||||
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
||||
|
||||
zerotierNetworkIdPath = machines: machineDir + machines + "/facts/zerotier-network-id";
|
||||
networkIdsUnchecked = builtins.map (
|
||||
machine:
|
||||
let
|
||||
fullPath = zerotierNetworkIdPath machine;
|
||||
in
|
||||
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
||||
) machines;
|
||||
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
||||
networkId = if builtins.length networkIds == 0 then null else builtins.elemAt networkIds 0;
|
||||
in
|
||||
#TODO:trace on multiple found network-ids
|
||||
#TODO:trace on no single found networkId
|
||||
{
|
||||
options.clan.zerotier-static-peers = {
|
||||
excludeHosts = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ config.clanCore.machineName ];
|
||||
description = "Hosts that should be excluded";
|
||||
};
|
||||
};
|
||||
|
||||
config.systemd.services.zerotier-static-peers-autoaccept =
|
||||
let
|
||||
machines = builtins.readDir machineDir;
|
||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||
filteredMachines = lib.filterAttrs (
|
||||
name: _: !(lib.elem name config.clan.zerotier-static-peers.excludeHosts)
|
||||
) machines;
|
||||
hosts = lib.mapAttrsToList (host: _: host) (
|
||||
lib.mapAttrs' (
|
||||
machine: _:
|
||||
let
|
||||
fullPath = zerotierIpMachinePath machine;
|
||||
in
|
||||
if builtins.pathExists fullPath then
|
||||
lib.nameValuePair (builtins.readFile fullPath) [ machine ]
|
||||
else
|
||||
null
|
||||
) filteredMachines
|
||||
);
|
||||
in
|
||||
lib.mkIf (config.clan.networking.zerotier.controller.enable) {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "zerotierone.service" ];
|
||||
path = [ pkgs.zerotierone ];
|
||||
serviceConfig.ExecStart = pkgs.writeScript "static-zerotier-peers-autoaccept" ''
|
||||
#!/bin/sh
|
||||
${lib.concatMapStringsSep "\n" (host: ''
|
||||
${
|
||||
inputs.clan-core.packages.${pkgs.system}.zerotier-members
|
||||
}/bin/zerotier-members allow --member-ip ${host}
|
||||
'') hosts}
|
||||
'';
|
||||
};
|
||||
|
||||
config.clan.networking.zerotier.networkId = lib.mkDefault networkId;
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
Enable ZeroTier VPN over TCP for networks where UDP is blocked.
|
||||
---
|
@ -1,31 +0,0 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
options.clan.zt-tcp-relay = {
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 4443;
|
||||
description = "Port to listen on";
|
||||
};
|
||||
};
|
||||
config = {
|
||||
networking.firewall.allowedTCPPorts = [ config.clan.zt-tcp-relay.port ];
|
||||
|
||||
systemd.services.zt-tcp-relay = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${
|
||||
pkgs.callPackage ../../pkgs/zt-tcp-relay { }
|
||||
}/bin/zt-tcp-relay --listen [::]:${builtins.toString config.clan.zt-tcp-relay.port}";
|
||||
Restart = "always";
|
||||
RestartSec = "5";
|
||||
dynamicUsers = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
34
devShell.nix
34
devShell.nix
@ -1,34 +1,13 @@
|
||||
{ ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
self',
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
writers = pkgs.callPackage ./pkgs/builders/script-writers.nix { };
|
||||
|
||||
ansiEscapes = {
|
||||
reset = ''\033[0m'';
|
||||
green = ''\033[32m'';
|
||||
};
|
||||
|
||||
# A python program to switch between dev-shells
|
||||
# usage: select-shell shell-name
|
||||
# the currently enabled dev-shell gets stored in ./.direnv/selected-shell
|
||||
select-shell = writers.writePython3Bin "select-shell" {
|
||||
flakeIgnore = [ "E501" ];
|
||||
} ./pkgs/scripts/select-shell.py;
|
||||
in
|
||||
{
|
||||
{ pkgs
|
||||
, self'
|
||||
, config
|
||||
, ...
|
||||
}: {
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
select-shell
|
||||
pkgs.tea
|
||||
# Better error messages than nix 2.18
|
||||
pkgs.nixVersions.latest
|
||||
self'.packages.tea-create-pr
|
||||
self'.packages.merge-after-ci
|
||||
self'.packages.pending-reviews
|
||||
@ -36,7 +15,8 @@
|
||||
config.treefmt.build.wrapper
|
||||
];
|
||||
shellHook = ''
|
||||
echo -e "${ansiEscapes.green}switch to another dev-shell using: select-shell${ansiEscapes.reset}"
|
||||
# no longer used
|
||||
rm -f "$(git rev-parse --show-toplevel)/.git/hooks/pre-commit"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +0,0 @@
|
||||
source_up
|
||||
|
||||
watch_file $(find ./nix -name "*.nix" -printf '%p ')
|
||||
|
||||
# Because we depend on nixpkgs sources, uploading to builders takes a long time
|
||||
use flake .#docs --builders ''
|
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
/site/reference
|
||||
/site/static/Roboto-Regular.ttf
|
||||
/site/static/FiraCode-VF.ttf
|
@ -1,195 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
**Continuous Integration (CI)**: Each pull request gets automatically tested by gitea. If any errors are detected, it will block pull requests until they're resolved.
|
||||
|
||||
**Dependency Management**: We use the [Nix package manager](https://nixos.org/) to manage dependencies and ensure reproducibility, making your development process more robust.
|
||||
|
||||
## Supported Operating Systems
|
||||
|
||||
- Linux
|
||||
- macOS
|
||||
|
||||
# Getting Started with the Development Environment
|
||||
|
||||
Let's get your development environment up and running:
|
||||
|
||||
1. **Install Nix Package Manager**:
|
||||
|
||||
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||
```
|
||||
|
||||
2. **Install direnv**:
|
||||
|
||||
- To automatically setup a devshell on entering the directory
|
||||
```bash
|
||||
nix profile install nixpkgs#nix-direnv-flakes
|
||||
```
|
||||
|
||||
3. **Add direnv to your shell**:
|
||||
|
||||
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
||||
|
||||
```bash
|
||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||
```
|
||||
|
||||
4. **Create a Gitea Account**:
|
||||
- Register an account on https://git.clan.lol
|
||||
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
||||
- Clone the repository and navigate to it
|
||||
- Add a new remote called upstream:
|
||||
```bash
|
||||
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||
```
|
||||
|
||||
5. **Register Your Gitea Account Locally**:
|
||||
|
||||
- Execute the following command to add your Gitea account locally:
|
||||
```bash
|
||||
tea login add
|
||||
```
|
||||
- Fill out the prompt as follows:
|
||||
- URL of Gitea instance: `https://git.clan.lol`
|
||||
- Name of new Login [git.clan.lol]:
|
||||
- Do you have an access token? No
|
||||
- Username: YourUsername
|
||||
- Password: YourPassword
|
||||
- Set Optional settings: No
|
||||
|
||||
|
||||
6. **Allow .envrc**:
|
||||
|
||||
- When you enter the directory, you'll receive an error message like this:
|
||||
```bash
|
||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||
```
|
||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||
|
||||
7. **(Optional) Install Git Hooks**:
|
||||
- To syntax check your code you can run:
|
||||
```bash
|
||||
nix fmt
|
||||
```
|
||||
- To make this automatic install the git hooks
|
||||
```bash
|
||||
./scripts/pre-commit
|
||||
```
|
||||
|
||||
8. **Open a Pull Request**:
|
||||
- To automatically open up a pull request you can use our tool called:
|
||||
```
|
||||
merge-after-ci --reviewers Mic92 Lassulus Qubasa
|
||||
```
|
||||
|
||||
# Debugging
|
||||
|
||||
Here are some methods for debugging and testing the clan-cli:
|
||||
|
||||
## See all possible packages and tests
|
||||
|
||||
To quickly show all possible packages and tests execute:
|
||||
|
||||
```bash
|
||||
nix flake show --system no-eval
|
||||
```
|
||||
|
||||
Under `checks` you will find all tests that are executed in our CI. Under `packages` you find all our projects.
|
||||
|
||||
```
|
||||
git+file:///home/lhebendanz/Projects/clan-core
|
||||
├───apps
|
||||
│ └───x86_64-linux
|
||||
│ ├───install-vm: app
|
||||
│ └───install-vm-nogui: app
|
||||
├───checks
|
||||
│ └───x86_64-linux
|
||||
│ ├───borgbackup omitted (use '--all-systems' to show)
|
||||
│ ├───check-for-breakpoints omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-age omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-bash omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-e2fsprogs omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-fakeroot omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-git omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-nix omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-openssh omitted (use '--all-systems' to show)
|
||||
│ ├───"clan-dep-python3.11-mypy" omitted (use '--all-systems' to show)
|
||||
├───packages
|
||||
│ └───x86_64-linux
|
||||
│ ├───clan-cli omitted (use '--all-systems' to show)
|
||||
│ ├───clan-cli-docs omitted (use '--all-systems' to show)
|
||||
│ ├───clan-ts-api omitted (use '--all-systems' to show)
|
||||
│ ├───clan-vm-manager omitted (use '--all-systems' to show)
|
||||
│ ├───default omitted (use '--all-systems' to show)
|
||||
│ ├───deploy-docs omitted (use '--all-systems' to show)
|
||||
│ ├───docs omitted (use '--all-systems' to show)
|
||||
│ ├───editor omitted (use '--all-systems' to show)
|
||||
└───templates
|
||||
├───default: template: Initialize a new clan flake
|
||||
└───new-clan: template: Initialize a new clan flake
|
||||
```
|
||||
|
||||
You can execute every test separately by following the tree path `nix build .#checks.x86_64-linux.clan-pytest` for example.
|
||||
|
||||
## Test Locally in Devshell with Breakpoints
|
||||
|
||||
To test the cli locally in a development environment and set breakpoints for debugging, follow these steps:
|
||||
|
||||
1. Run the following command to execute your tests and allow for debugging with breakpoints:
|
||||
```bash
|
||||
cd ./pkgs/clan-cli
|
||||
pytest -n0 -s --maxfail=1 ./tests/test_nameofthetest.py
|
||||
```
|
||||
You can place `breakpoint()` in your Python code where you want to trigger a breakpoint for debugging.
|
||||
|
||||
## Test Locally in a Nix Sandbox
|
||||
|
||||
To run tests in a Nix sandbox, you have two options depending on whether your test functions have been marked as impure or not:
|
||||
|
||||
### Running Tests Marked as Impure
|
||||
|
||||
If your test functions need to execute `nix build` and have been marked as impure because you can't execute `nix build` inside a Nix sandbox, use the following command:
|
||||
|
||||
```bash
|
||||
nix run .#impure-checks
|
||||
```
|
||||
|
||||
This command will run the impure test functions.
|
||||
|
||||
### Running Pure Tests
|
||||
|
||||
For test functions that have not been marked as impure and don't require executing `nix build`, you can use the following command:
|
||||
|
||||
```bash
|
||||
nix build .#checks.x86_64-linux.clan-pytest --rebuild
|
||||
```
|
||||
|
||||
This command will run all pure test functions.
|
||||
|
||||
### Inspecting the Nix Sandbox
|
||||
|
||||
If you need to inspect the Nix sandbox while running tests, follow these steps:
|
||||
|
||||
1. Insert an endless sleep into your test code where you want to pause the execution. For example:
|
||||
|
||||
```python
|
||||
import time
|
||||
time.sleep(3600) # Sleep for one hour
|
||||
```
|
||||
|
||||
2. Use `cntr` and `psgrep` to attach to the Nix sandbox. This allows you to interactively debug your code while it's paused. For example:
|
||||
|
||||
```bash
|
||||
psgrep -a -x your_python_process_name
|
||||
cntr attach <container id, container name or process id>
|
||||
```
|
||||
|
||||
Or you can also use the [nix breakpoint hook](https://nixos.org/manual/nixpkgs/stable/#breakpointhook)
|
||||
|
||||
|
||||
# Standards
|
||||
|
||||
- Every new module name should be in kebab-case.
|
||||
- Every fact definition, where possible should be in kebab-case.
|
138
docs/api-guidelines.md
Normal file
138
docs/api-guidelines.md
Normal file
@ -0,0 +1,138 @@
|
||||
# API Guidelines
|
||||
|
||||
This issue serves to collect our common understanding how to design our API so that it is extensible and usable and understandable.
|
||||
|
||||
## Resource oriented
|
||||
|
||||
A resource-oriented API is generally modeled as a resource hierarchy, where each node is either a simple resource or a collection resource. For convenience, they are often called a resource and a collection, respectively.
|
||||
|
||||
Examples of Resource Nouns:
|
||||
|
||||
`machine`
|
||||
`user`
|
||||
`flake`
|
||||
|
||||
Often resources have sub-resources. Even if it is not foreseen, it is recommended to use plural (trailing `s`) on resources to allow them to be collections of sub-resources.
|
||||
|
||||
e.g,
|
||||
|
||||
`users`
|
||||
->
|
||||
`users/*/profile`
|
||||
|
||||
## Verbs
|
||||
|
||||
Verbs should not be part of the URL
|
||||
|
||||
Bad:
|
||||
`/api/create-products`
|
||||
|
||||
Good:
|
||||
`/api/products`
|
||||
|
||||
Only resources are part of the URL, verbs are described via the HTTP Method.
|
||||
|
||||
Exception:
|
||||
|
||||
If a different HTTP Method must be used for technical reasons it is okay to terminate the path with a (short) verb / action.
|
||||
|
||||
Okay ish:
|
||||
`/api/products/create`
|
||||
|
||||
## Usually the following HTTP Methods exist to interact with a resource
|
||||
|
||||
- POST (create an order for a resource)
|
||||
- GET (retrieve the information)
|
||||
- PUT (update and replace information)
|
||||
- PATCH (update and modify information) **(Not used yet)**
|
||||
- DELETE (delete the item)
|
||||
|
||||
## Every resource should be CRUD compatible
|
||||
|
||||
All API resources MUST be designed in a way that allows the typical CRUD operations.
|
||||
|
||||
Where crud stands for:
|
||||
|
||||
C - Create
|
||||
R - Read
|
||||
U - Update
|
||||
D - Delete
|
||||
|
||||
Resources should implement at least a "Read" operation.
|
||||
|
||||
## Body
|
||||
|
||||
Use JSON as an exchange format.
|
||||
|
||||
All responses MUST be JSON parseable.
|
||||
|
||||
Bad:
|
||||
`bare string`
|
||||
|
||||
Better:
|
||||
`"quoted string"`
|
||||
|
||||
Best: (Enveloped see next section)
|
||||
`{ name: "quoted string"}`
|
||||
|
||||
Errors should have a consistent JSON format, such that it is clear in which field to look at for displaying error messages.
|
||||
|
||||
## Envelop all Data collections
|
||||
|
||||
Response data should be wrapped into an JSON Object `{}`
|
||||
Lists `[]` should also contain Objects `{}`.
|
||||
This allows everything, to be extensible, without breaking backwards compatibility. (Adding fields is trivial, since the schema doesn't change)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
{
|
||||
"users": [{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
…
|
||||
}, {
|
||||
first_name: "Jane",
|
||||
last_name: "Doe",
|
||||
…
|
||||
}
|
||||
....
|
||||
],
|
||||
"skip": 0,
|
||||
"limit": 20,
|
||||
....
|
||||
}
|
||||
```
|
||||
|
||||
Bad Example of a breaking change:
|
||||
`GET /api/flakes`
|
||||
`old`
|
||||
|
||||
```
|
||||
[
|
||||
"dream2nix"
|
||||
"disko"
|
||||
]
|
||||
```
|
||||
|
||||
`new`
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
name: "dream2nix",
|
||||
url: "github/...."
|
||||
},
|
||||
{
|
||||
name: "disko",
|
||||
url: "github/...."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Those kind of breaking changes can be avoided by using an object from the beginning.
|
||||
Even if the object only contains one key, it is extensible, without breaking.
|
||||
|
||||
## More will follow.
|
||||
|
||||
...maybe
|
69
docs/clan-config.md
Normal file
69
docs/clan-config.md
Normal file
@ -0,0 +1,69 @@
|
||||
# cLAN config
|
||||
|
||||
`clan config` allows you to manage your nixos configuration via the terminal.
|
||||
Similar as how `git config` reads and sets git options, `clan config` does the same with your nixos options
|
||||
It also supports auto completion making it easy to find the right options.
|
||||
|
||||
## Set up clan-config
|
||||
|
||||
Add the clan tool to your flake inputs:
|
||||
|
||||
```
|
||||
clan.url = "git+https://git.clan.lol/clan/clan-core";
|
||||
```
|
||||
|
||||
and inside the mkFlake:
|
||||
|
||||
```
|
||||
imports = [
|
||||
inputs.clan.flakeModules.clan-config
|
||||
];
|
||||
```
|
||||
|
||||
Add an empty config file and add it to git
|
||||
|
||||
```command
|
||||
echo "{}" > ./clan-settings.json
|
||||
git add ./clan-settings.json
|
||||
```
|
||||
|
||||
Import the clan-config module into your nixos configuration:
|
||||
|
||||
```nix
|
||||
{
|
||||
imports = [
|
||||
# clan-settings.json is located in the same directory as your flake.
|
||||
# Adapt the path if necessary.
|
||||
(builtins.fromJSON (builtins.readFile ./clan-settings.json))
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
Make sure your nixos configuration is set a default
|
||||
|
||||
```nix
|
||||
{self, ...}: {
|
||||
flake.nixosConfigurations.default = self.nixosConfigurations.my-machine;
|
||||
}
|
||||
```
|
||||
|
||||
Use all inputs provided by the clan-config devShell in your own devShell:
|
||||
|
||||
```nix
|
||||
{ ... }: {
|
||||
perSystem = { pkgs, self', ... }: {
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = [ self'.devShells.clan-config ];
|
||||
# ...
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
re-load your dev-shell to make the clan tool available.
|
||||
|
||||
```command
|
||||
clan config --help
|
||||
```
|
227
docs/contributing.md
Normal file
227
docs/contributing.md
Normal file
@ -0,0 +1,227 @@
|
||||
# Website Template
|
||||
|
||||
Welcome to our website template repository! This template is designed to help you and your team build high-quality websites efficiently. We've carefully chosen the technologies to make development smooth and enjoyable. Here's what you can expect from this template:
|
||||
|
||||
**Frontend**: Our frontend is powered by [React NextJS](https://nextjs.org/), a popular and versatile framework for building web applications.
|
||||
|
||||
**Backend**: For the backend, we use Python along with the [FastAPI framework](https://fastapi.tiangolo.com/). To ensure seamless communication between the frontend and backend, we generate an `openapi.json` file from the Python code, which defines the REST API. This file is then used with [Orval](https://orval.dev/) to generate TypeScript bindings for the REST API. We're committed to code correctness, so we use [mypy](https://mypy-lang.org/) to ensure that our Python code is statically typed correctly. For backend testing, we rely on [pytest](https://docs.pytest.org/en/7.4.x/).
|
||||
|
||||
**Continuous Integration (CI)**: We've set up a CI bot that rigorously checks your code using the quality assurance (QA) tools mentioned above. If any errors are detected, it will block pull requests until they're resolved.
|
||||
|
||||
**Dependency Management**: We use the [Nix package manager](https://nixos.org/) to manage dependencies and ensure reproducibility, making your development process more robust.
|
||||
|
||||
## Supported Operating Systems
|
||||
|
||||
- Linux
|
||||
- macOS
|
||||
|
||||
# Getting Started with the Development Environment
|
||||
|
||||
Let's get your development environment up and running:
|
||||
|
||||
1. **Install Nix Package Manager**:
|
||||
|
||||
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||
```
|
||||
|
||||
2. **Install direnv**:
|
||||
|
||||
- Download the direnv package from [here](https://direnv.net/docs/installation.html) or run the following command:
|
||||
```bash
|
||||
curl -sfL https://direnv.net/install.sh | bash
|
||||
```
|
||||
|
||||
3. **Add direnv to your shell**:
|
||||
|
||||
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||
You can do this by executing following command:
|
||||
|
||||
```bash
|
||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||
```
|
||||
|
||||
4. **Clone the Repository and Navigate**:
|
||||
|
||||
- Clone this repository and navigate to it.
|
||||
|
||||
5. **Allow .envrc**:
|
||||
|
||||
- When you enter the directory, you'll receive an error message like this:
|
||||
```bash
|
||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||
```
|
||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||
|
||||
6. **Build the Backend**:
|
||||
|
||||
- Go to the `pkgs/clan-cli` directory and execute:
|
||||
```bash
|
||||
direnv allow
|
||||
```
|
||||
- Wait for the backend to build.
|
||||
|
||||
7. **Start the Backend Server**:
|
||||
|
||||
- To start the backend server, execute:
|
||||
```bash
|
||||
clan webui --reload --no-open --log-level debug
|
||||
```
|
||||
- The server will automatically restart if any Python files change.
|
||||
|
||||
8. **Build the Frontend**:
|
||||
|
||||
- In a different shell, navigate to the `pkgs/ui` directory and execute:
|
||||
```bash
|
||||
direnv allow
|
||||
```
|
||||
- Wait for the frontend to build.
|
||||
|
||||
NOTE: If you have the error "@clan/colors.json" you executed `npm install`, please do not do that. `direnv reload` will handle dependency management. Please delete node_modules with `rm -rf node_modules`.
|
||||
|
||||
9. **Start the Frontend**:
|
||||
- To start the frontend, execute:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
- Access the website by going to [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
# Setting Up Your Git Workflow
|
||||
|
||||
Let's set up your Git workflow to collaborate effectively:
|
||||
|
||||
1. **Register Your Gitea Account Locally**:
|
||||
|
||||
- Execute the following command to add your Gitea account locally:
|
||||
```bash
|
||||
tea login add
|
||||
```
|
||||
- Fill out the prompt as follows:
|
||||
- URL of Gitea instance: `https://gitea.gchq.icu`
|
||||
- Name of new Login [gitea.gchq.icu]: `gitea.gchq.icu:7171`
|
||||
- Do you have an access token? No
|
||||
- Username: YourUsername
|
||||
- Password: YourPassword
|
||||
- Set Optional settings: No
|
||||
|
||||
2. **Git Workflow**:
|
||||
|
||||
1. Add your changes to Git using `git add <file1> <file2>`.
|
||||
2. Run `nix fmt` to lint your files.
|
||||
3. Commit your changes with a descriptive message: `git commit -a -m "My descriptive commit message"`.
|
||||
4. Make sure your branch has the latest changes from upstream by executing:
|
||||
```bash
|
||||
git fetch && git rebase origin/main --autostash
|
||||
```
|
||||
5. Use `git status` to check for merge conflicts.
|
||||
6. If conflicts exist, resolve them. Here's a tutorial for resolving conflicts in [VSCode](https://code.visualstudio.com/docs/sourcecontrol/overview#_merge-conflicts).
|
||||
7. After resolving conflicts, execute `git merge --continue` and repeat step 5 until there are no conflicts.
|
||||
|
||||
3. **Create a Pull Request**:
|
||||
|
||||
- To automatically open a pull request that gets merged if all tests pass, execute:
|
||||
```bash
|
||||
merge-after-ci
|
||||
```
|
||||
|
||||
4. **Review Your Pull Request**:
|
||||
|
||||
- Visit https://gitea.gchq.icu and go to the project page. Check under "Pull Requests" for any issues with your pull request.
|
||||
|
||||
5. **Push Your Changes**:
|
||||
- If there are issues, fix them and redo step 2. Afterward, execute:
|
||||
```bash
|
||||
git push origin HEAD:YourUsername-main
|
||||
```
|
||||
- This will directly push to your open pull request.
|
||||
|
||||
# Debugging
|
||||
|
||||
When working on the backend of your project, debugging is an essential part of the development process. Here are some methods for debugging and testing the backend of your application:
|
||||
|
||||
## Test Backend Locally in Devshell with Breakpoints
|
||||
|
||||
To test the backend locally in a development environment and set breakpoints for debugging, follow these steps:
|
||||
|
||||
1. Run the following command to execute your tests and allow for debugging with breakpoints:
|
||||
```bash
|
||||
pytest -n0 -s --maxfail=1
|
||||
```
|
||||
You can place `breakpoint()` in your Python code where you want to trigger a breakpoint for debugging.
|
||||
|
||||
## Test Backend Locally in a Nix Sandbox
|
||||
|
||||
To run your backend tests in a Nix sandbox, you have two options depending on whether your test functions have been marked as impure or not:
|
||||
|
||||
### Running Tests Marked as Impure
|
||||
|
||||
If your test functions need to execute `nix build` and have been marked as impure because you can't execute `nix build` inside a Nix sandbox, use the following command:
|
||||
|
||||
```bash
|
||||
nix run .#impure-checks
|
||||
```
|
||||
|
||||
This command will run the impure test functions.
|
||||
|
||||
### Running Pure Tests
|
||||
|
||||
For test functions that have not been marked as impure and don't require executing `nix build`, you can use the following command:
|
||||
|
||||
```bash
|
||||
nix build .#checks.x86_64-linux.clan-pytest --rebuild
|
||||
```
|
||||
|
||||
This command will run all pure test functions.
|
||||
|
||||
### Running schemathesis fuzzer on GET requests
|
||||
|
||||
```bash
|
||||
nix run .#runSchemaTests
|
||||
```
|
||||
|
||||
If you want to test more request types edit the file `checks/impure/flake-module.nix`
|
||||
|
||||
### Inspecting the Nix Sandbox
|
||||
|
||||
If you need to inspect the Nix sandbox while running tests, follow these steps:
|
||||
|
||||
1. Insert an endless sleep into your test code where you want to pause the execution. For example:
|
||||
|
||||
```python
|
||||
import time
|
||||
time.sleep(3600) # Sleep for one hour
|
||||
```
|
||||
|
||||
2. Use `cntr` and `psgrep` to attach to the Nix sandbox. This allows you to interactively debug your code while it's paused. For example:
|
||||
|
||||
```bash
|
||||
cntr exec -w your_sandbox_name
|
||||
psgrep -a -x your_python_process_name
|
||||
```
|
||||
|
||||
These debugging and testing methods will help you identify and fix issues in your backend code efficiently, ensuring the reliability and robustness of your application.
|
||||
|
||||
For more information on testing read [property and contract based testing](testing.md)
|
||||
|
||||
# Using this Template
|
||||
|
||||
To make the most of this template:
|
||||
|
||||
1. Set up a new Gitea account named `ui-asset-bot`. Generate an access token with all access permissions and set it under `settings/actions/secrets` as a secret called `BOT_ACCESS_TOKEN`.
|
||||
|
||||
- Also, edit the file `.gitea/workflows/ui_assets.yaml` and change the `BOT_EMAIL` variable to match the email you set for that account. Gitea matches commits to accounts by their email address, so this step is essential.
|
||||
|
||||
2. Create a second Gitea account named `merge-bot`. Edit the file `pkgs/merge-after-ci/default.nix` if the name should be different. Under "Branches," set the main branch to be protected and add `merge-bot` to the whitelisted users for pushing. Set the unprotected file pattern to `**/ui-assets.nix`.
|
||||
|
||||
- Enable the status check for "build / test (pull_request)."
|
||||
|
||||
3. Add both `merge-bot` and `ui-asset-bot` as collaborators.
|
||||
- Set the option to "Delete pull request branch after merge by default."
|
||||
- Also, set the default merge style to "Rebase then create merge commit."
|
||||
|
||||
With this template, you're well-equipped to build and collaborate on high-quality websites efficiently. Happy coding!.
|
||||
|
||||
# API guidelines
|
||||
|
||||
see [./api-guidelines](./api-guidelines)
|
115
docs/machines.md
Normal file
115
docs/machines.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Managing NixOS Machines
|
||||
|
||||
## Add Your First Machine
|
||||
|
||||
To start managing a new machine, use the following commands to create and then list your machines:
|
||||
|
||||
```shellSession
|
||||
$ clan machines create my-machine
|
||||
$ clan machines list
|
||||
my-machine
|
||||
```
|
||||
|
||||
## Configure Your Machine
|
||||
|
||||
In the example below, we demonstrate how to add a new user named `my-user` and set a password. This user will be configured to log in to the machine `my-machine`.
|
||||
|
||||
### Creating a New User
|
||||
|
||||
```shellSession
|
||||
# Add a new user
|
||||
$ clan config --machine my-machine users.users.my-user.isNormalUser true
|
||||
|
||||
# Set a password for the user
|
||||
$ clan config --machine my-machine users.users.my-user.hashedPassword $(mkpasswd)
|
||||
```
|
||||
|
||||
_Note: The `$(mkpasswd)` command generates a hashed password. Ensure you have the `mkpasswd` utility installed or use an alternative method to generate a secure hashed password._
|
||||
|
||||
## Test Your Machine Configuration Inside a VM
|
||||
|
||||
Before deploying your configuration to a live environment, you can run a virtual machine (VM) to test the settings:
|
||||
|
||||
```shellSession
|
||||
$ clan vms run my-machine
|
||||
```
|
||||
|
||||
This command run a VM based on the configuration of `my-machine`, allowing you to verify changes in a controlled environment.
|
||||
|
||||
## Installing a New Machine
|
||||
|
||||
Clan CLI, in conjunction with [nixos-anywhere](https://github.com/nix-community/nixos-anywhere), provides a seamless method for installing NixOS on various machines.
|
||||
This process involves preparing a suitable hardware and disk partitioning configuration and ensuring the target machine is accessible via SSH.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- A running Linux system with SSH on the target machine is required. This is typically pre-configured for many server providers.
|
||||
- For installations on physical hardware, create a NixOS installer image and transfer it to a bootable USB drive as described below.
|
||||
|
||||
## Creating a Bootable USB Drive on Linux
|
||||
|
||||
To create a bootable USB flash drive with the NixOS installer:
|
||||
|
||||
1. **Build the Installer Image**:
|
||||
|
||||
```shellSession
|
||||
$ nix build git+https://git.clan.lol/clan/clan-core.git#install-iso
|
||||
```
|
||||
|
||||
2. **Prepare the USB Flash Drive**:
|
||||
|
||||
- Insert your USB flash drive into your computer.
|
||||
- Identify your flash drive with `lsblk`. Look for the device with a matching size.
|
||||
- Ensure all partitions on the drive are unmounted. Replace `sdX` in the command below with your device identifier (like `sdb`, etc.):
|
||||
|
||||
```shellSession
|
||||
sudo umount /dev/sdX*
|
||||
```
|
||||
|
||||
3. **Write the Image to the USB Drive**:
|
||||
|
||||
- Use the `dd` utility to write the NixOS installer image to your USB drive:
|
||||
|
||||
```shellSession
|
||||
sudo dd bs=4M conv=fsync oflag=direct status=progress if=./result/stick.raw of=/dev/sdX
|
||||
```
|
||||
|
||||
4. **Boot and Connect**:
|
||||
- After writing the installer to the USB drive, use it to boot the target machine.
|
||||
- The installer will display an IP address and a root password, which you can use to connect via SSH.
|
||||
|
||||
### Finishing the installation
|
||||
|
||||
With the target machine running Linux and accessible via SSH, execute the following command to install NixOS on the target machine, replacing `<target_host>` with the machine's hostname or IP address:
|
||||
|
||||
```shellSession
|
||||
$ clan machines install my-machine <target_host>
|
||||
```
|
||||
|
||||
## Update Your Machines
|
||||
|
||||
Clan CLI enables you to remotely update your machines over SSH. This requires setting up a deployment address for each target machine.
|
||||
|
||||
### Setting the Deployment Address
|
||||
|
||||
Replace `host_or_ip` with the actual hostname or IP address of your target machine:
|
||||
|
||||
```shellSession
|
||||
$ clan config --machine my-machine clan.networking.deploymentAddress root@host_or_ip
|
||||
```
|
||||
|
||||
_Note: The use of `root@` in the deployment address implies SSH access as the root user. Ensure that the root login is secured and only used when necessary._
|
||||
|
||||
### Updating Machine Configurations
|
||||
|
||||
Execute the following command to update the specified machine:
|
||||
|
||||
```shellSession
|
||||
$ clan machines update my-machine
|
||||
```
|
||||
|
||||
You can also update all configured machines simultaneously by omitting the machine name:
|
||||
|
||||
```shellSession
|
||||
$ clan machines update
|
||||
```
|
41
docs/main.py
41
docs/main.py
@ -1,41 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
def define_env(env: Any) -> None:
|
||||
static_dir = "/static/"
|
||||
video_dir = "https://clan.lol/" + "videos/"
|
||||
asciinema_dir = static_dir + "asciinema-player/"
|
||||
|
||||
@env.macro
|
||||
def video(name: str) -> str:
|
||||
return f"""<video loop muted autoplay id="{name}">
|
||||
<source src={video_dir + name} type="video/webm">
|
||||
Your browser does not support the video tag.
|
||||
</video>"""
|
||||
|
||||
@env.macro
|
||||
def asciinema(name: str) -> str:
|
||||
return f"""<div id="{name}">
|
||||
<script>
|
||||
// Function to load the script and then create the Asciinema player
|
||||
function loadAsciinemaPlayer() {{
|
||||
var script = document.createElement('script');
|
||||
script.src = "{asciinema_dir}/asciinema-player.min.js";
|
||||
script.onload = function() {{
|
||||
AsciinemaPlayer.create('{video_dir + name}', document.getElementById("{name}"), {{
|
||||
loop: true,
|
||||
autoPlay: true,
|
||||
controls: false,
|
||||
speed: 1.5,
|
||||
theme: "solarized-light"
|
||||
}});
|
||||
}};
|
||||
document.head.appendChild(script);
|
||||
}}
|
||||
|
||||
// Load the Asciinema player script
|
||||
loadAsciinemaPlayer();
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{asciinema_dir}/asciinema-player.css" />
|
||||
</div>"""
|
159
docs/mkdocs.yml
159
docs/mkdocs.yml
@ -1,159 +0,0 @@
|
||||
site_name: Clan Documentation
|
||||
site_url: https://docs.clan.lol
|
||||
repo_url: https://git.clan.lol/clan/clan-core/
|
||||
repo_name: clan-core
|
||||
edit_uri: _edit/main/docs/docs/
|
||||
|
||||
validation:
|
||||
omitted_files: warn
|
||||
absolute_links: warn
|
||||
unrecognized_links: warn
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- attr_list
|
||||
- footnotes
|
||||
- md_in_html
|
||||
- meta
|
||||
- plantuml_markdown
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.details
|
||||
- pymdownx.highlight:
|
||||
use_pygments: true
|
||||
anchor_linenums: true
|
||||
- pymdownx.keys
|
||||
- toc:
|
||||
title: On this page
|
||||
|
||||
exclude_docs: |
|
||||
.*
|
||||
!templates/
|
||||
/drafts/
|
||||
|
||||
nav:
|
||||
- Blog:
|
||||
- blog/index.md
|
||||
- Getting started:
|
||||
- index.md
|
||||
- Installer: getting-started/installer.md
|
||||
- Configure: getting-started/configure.md
|
||||
- Secrets & Facts: getting-started/secrets.md
|
||||
- Deploy Machine: getting-started/deploy.md
|
||||
- Mesh VPN: getting-started/mesh-vpn.md
|
||||
- Backup & Restore: getting-started/backups.md
|
||||
- Flake-parts: getting-started/flake-parts.md
|
||||
- Modules:
|
||||
- Clan Modules:
|
||||
- reference/clanModules/borgbackup.md
|
||||
- reference/clanModules/deltachat.md
|
||||
- reference/clanModules/disk-layouts.md
|
||||
- reference/clanModules/ergochat.md
|
||||
- reference/clanModules/localbackup.md
|
||||
- reference/clanModules/localsend.md
|
||||
- reference/clanModules/matrix-synapse.md
|
||||
- reference/clanModules/moonlight.md
|
||||
- reference/clanModules/root-password.md
|
||||
- reference/clanModules/sshd.md
|
||||
- reference/clanModules/sunshine.md
|
||||
- reference/clanModules/syncthing.md
|
||||
- reference/clanModules/static-hosts.md
|
||||
- reference/clanModules/thelounge.md
|
||||
- reference/clanModules/trusted-nix-caches.md
|
||||
- reference/clanModules/user-password.md
|
||||
- reference/clanModules/xfce.md
|
||||
- reference/clanModules/zerotier-static-peers.md
|
||||
- reference/clanModules/zt-tcp-relay.md
|
||||
- CLI:
|
||||
- reference/cli/index.md
|
||||
- reference/cli/backups.md
|
||||
- reference/cli/config.md
|
||||
- reference/cli/facts.md
|
||||
- reference/cli/flakes.md
|
||||
- reference/cli/flash.md
|
||||
- reference/cli/history.md
|
||||
- reference/cli/machines.md
|
||||
- reference/cli/secrets.md
|
||||
- reference/cli/ssh.md
|
||||
- reference/cli/vms.md
|
||||
- Clan Core:
|
||||
- reference/clan-core/index.md
|
||||
- reference/clan-core/backups.md
|
||||
- reference/clan-core/facts.md
|
||||
- reference/clan-core/sops.md
|
||||
- reference/clan-core/state.md
|
||||
- Contributing: contributing/contributing.md
|
||||
|
||||
docs_dir: site
|
||||
site_dir: out
|
||||
|
||||
theme:
|
||||
font: false
|
||||
logo: https://clan.lol/static/logo/clan-white.png
|
||||
favicon: https://clan.lol/static/dark-favicon/128x128.png
|
||||
name: material
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.tabs
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
- content.tabs.link
|
||||
icon:
|
||||
repo: fontawesome/brands/git-alt
|
||||
custom_dir: overrides
|
||||
|
||||
palette:
|
||||
# Palette toggle for light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: teal
|
||||
accent: deep purple
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
|
||||
# Palette toggle for dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
primary: teal
|
||||
accent: deep purple
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
|
||||
extra_css:
|
||||
- static/extra.css
|
||||
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/regular/comment
|
||||
link: https://matrix.to/#/#clan:lassul.us
|
||||
- icon: fontawesome/brands/gitlab
|
||||
link: https://git.clan.lol/clan/clan-core
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/clan-lol/clan-core
|
||||
- icon: fontawesome/solid/rss
|
||||
link: /feed_rss_created.xml
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- blog
|
||||
- macros
|
||||
- rss:
|
||||
match_path: blog/posts/.*
|
||||
use_git: false
|
||||
date_from_meta:
|
||||
as_creation: "date"
|
||||
as_update: "date"
|
||||
datetime_format: "%Y-%m-%d %H:%M"
|
||||
default_timezone: Europe/Paris
|
||||
default_time: "17:18"
|
||||
categories:
|
||||
- categories
|
||||
- tags
|
@ -1,52 +0,0 @@
|
||||
{
|
||||
pkgs,
|
||||
module-docs,
|
||||
clan-cli-docs,
|
||||
asciinema-player-js,
|
||||
asciinema-player-css,
|
||||
roboto,
|
||||
fira-code,
|
||||
...
|
||||
}:
|
||||
let
|
||||
uml-c4 = pkgs.python3Packages.plantuml-markdown.override { plantuml = pkgs.plantuml-c4; };
|
||||
in
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "clan-documentation";
|
||||
|
||||
src = ../.;
|
||||
|
||||
nativeBuildInputs =
|
||||
[
|
||||
pkgs.python3
|
||||
uml-c4
|
||||
]
|
||||
++ (with pkgs.python3Packages; [
|
||||
mkdocs
|
||||
mkdocs-material
|
||||
mkdocs-rss-plugin
|
||||
mkdocs-macros
|
||||
]);
|
||||
configurePhase = ''
|
||||
mkdir -p ./site/reference/cli
|
||||
cp -af ${module-docs}/* ./site/reference/
|
||||
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
||||
|
||||
mkdir -p ./site/static/asciinema-player
|
||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||
|
||||
# Link to fonts
|
||||
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||
ln -snf ${fira-code}/share/fonts/truetype/FiraCode-VF.ttf ./site/static/
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
mkdocs build --strict
|
||||
ls -la .
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
cp -a out/ $out/
|
||||
'';
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
{
|
||||
writeShellScriptBin,
|
||||
coreutils,
|
||||
openssh,
|
||||
rsync,
|
||||
lib,
|
||||
docs,
|
||||
}:
|
||||
|
||||
writeShellScriptBin "deploy-docs" ''
|
||||
set -eu -o pipefail
|
||||
export PATH="${
|
||||
lib.makeBinPath [
|
||||
coreutils
|
||||
openssh
|
||||
rsync
|
||||
]
|
||||
}"
|
||||
|
||||
#########################################
|
||||
# #
|
||||
# DO NOT PRINT THE SSH KEY TO THE LOGS #
|
||||
# #
|
||||
#########################################
|
||||
set +x
|
||||
if [ -n "''${SSH_HOMEPAGE_KEY:-}" ]; then
|
||||
echo "$SSH_HOMEPAGE_KEY" > ./ssh_key
|
||||
chmod 600 ./ssh_key
|
||||
sshExtraArgs="-i ./ssh_key"
|
||||
else
|
||||
sshExtraArgs=
|
||||
fi
|
||||
set -x
|
||||
###########################
|
||||
# #
|
||||
# END OF DANGER ZONE #
|
||||
# #
|
||||
###########################
|
||||
|
||||
|
||||
rsync \
|
||||
-e "ssh -o StrictHostKeyChecking=no $sshExtraArgs" \
|
||||
-a ${docs}/ \
|
||||
www@clan.lol:/var/www/docs.clan.lol
|
||||
|
||||
if [ -e ./ssh_key ]; then
|
||||
rm ./ssh_key
|
||||
fi
|
||||
''
|
@ -1,83 +0,0 @@
|
||||
{ inputs, self, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
config,
|
||||
self',
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# Simply evaluated options (JSON)
|
||||
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
|
||||
jsonDocs = import ./get-module-docs.nix {
|
||||
inherit (inputs) nixpkgs;
|
||||
inherit pkgs self;
|
||||
inherit (self.nixosModules) clanCore;
|
||||
inherit (self) clanModules;
|
||||
};
|
||||
|
||||
clanModulesFileInfo = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModules);
|
||||
clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes);
|
||||
|
||||
# Simply evaluated options (JSON)
|
||||
renderOptions =
|
||||
pkgs.runCommand "renderOptions.py"
|
||||
{
|
||||
# TODO: ruff does not splice properly in nativeBuildInputs
|
||||
depsBuildBuild = [ pkgs.ruff ];
|
||||
nativeBuildInputs = [
|
||||
pkgs.python3
|
||||
pkgs.mypy
|
||||
];
|
||||
}
|
||||
''
|
||||
install ${./scripts/renderOptions.py} $out
|
||||
patchShebangs --build $out
|
||||
|
||||
ruff format --check --diff $out
|
||||
ruff --line-length 88 $out
|
||||
mypy --strict $out
|
||||
'';
|
||||
|
||||
asciinema-player-js = pkgs.fetchurl {
|
||||
url = "https://github.com/asciinema/asciinema-player/releases/download/v3.7.0/asciinema-player.min.js";
|
||||
sha256 = "sha256-Ymco/+FinDr5YOrV72ehclpp4amrczjo5EU3jfr/zxs=";
|
||||
};
|
||||
asciinema-player-css = pkgs.fetchurl {
|
||||
url = "https://github.com/asciinema/asciinema-player/releases/download/v3.7.0/asciinema-player.css";
|
||||
sha256 = "sha256-GZMeZFFGvP5GMqqh516mjJKfQaiJ6bL38bSYOXkaohc=";
|
||||
};
|
||||
|
||||
module-docs = pkgs.runCommand "rendered" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
||||
export CLAN_CORE=${jsonDocs.clanCore}/share/doc/nixos/options.json
|
||||
# A file that contains the links to all clanModule docs
|
||||
export CLAN_MODULES=${clanModulesFileInfo}
|
||||
export CLAN_MODULES_READMES=${clanModulesReadmes}
|
||||
|
||||
mkdir $out
|
||||
|
||||
# The python script will place mkDocs files in the output directory
|
||||
python3 ${renderOptions}
|
||||
'';
|
||||
in
|
||||
{
|
||||
devShells.docs = pkgs.callPackage ./shell.nix {
|
||||
inherit (self'.packages) docs clan-cli-docs;
|
||||
inherit module-docs;
|
||||
inherit asciinema-player-js;
|
||||
inherit asciinema-player-css;
|
||||
};
|
||||
packages = {
|
||||
docs = pkgs.python3.pkgs.callPackage ./default.nix {
|
||||
inherit (self'.packages) clan-cli-docs;
|
||||
inherit (inputs) nixpkgs;
|
||||
inherit module-docs;
|
||||
inherit asciinema-player-js;
|
||||
inherit asciinema-player-css;
|
||||
};
|
||||
deploy-docs = pkgs.callPackage ./deploy-docs.nix { inherit (config.packages) docs; };
|
||||
inherit module-docs;
|
||||
};
|
||||
};
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
{
|
||||
nixpkgs,
|
||||
pkgs,
|
||||
clanCore,
|
||||
clanModules,
|
||||
self,
|
||||
}:
|
||||
let
|
||||
allNixosModules = (import "${nixpkgs}/nixos/modules/module-list.nix") ++ [
|
||||
"${nixpkgs}/nixos/modules/misc/assertions.nix"
|
||||
{ nixpkgs.hostPlatform = "x86_64-linux"; }
|
||||
];
|
||||
|
||||
clanCoreNixosModules = [
|
||||
clanCore
|
||||
{ clanCore.clanDir = ./.; }
|
||||
] ++ allNixosModules;
|
||||
|
||||
# TODO: optimally we would not have to evaluate all nixos modules for every page
|
||||
# but some of our module options secretly depend on nixos modules.
|
||||
# We would have to get rid of these implicit dependencies and make them explicit
|
||||
clanCoreNixos = pkgs.nixos { imports = clanCoreNixosModules; };
|
||||
|
||||
# using extendModules here instead of re-evaluating nixos every time
|
||||
# improves eval performance slightly (10%)
|
||||
getOptions = modules: (clanCoreNixos.extendModules { inherit modules; }).options;
|
||||
|
||||
evalDocs =
|
||||
options:
|
||||
pkgs.nixosOptionsDoc {
|
||||
options = options;
|
||||
warningsAreErrors = false;
|
||||
};
|
||||
|
||||
# clanModules docs
|
||||
clanModulesDocs = builtins.mapAttrs (
|
||||
name: module: (evalDocs ((getOptions [ module ]).clan.${name} or { })).optionsJSON
|
||||
) clanModules;
|
||||
|
||||
clanModulesReadmes = builtins.mapAttrs (
|
||||
module_name: _module: self.lib.modules.getReadme module_name
|
||||
) clanModules;
|
||||
|
||||
# clanCore docs
|
||||
clanCoreDocs = (evalDocs (getOptions [ ]).clanCore).optionsJSON;
|
||||
in
|
||||
{
|
||||
inherit clanModulesReadmes;
|
||||
clanCore = clanCoreDocs;
|
||||
clanModules = clanModulesDocs;
|
||||
}
|
@ -1,237 +0,0 @@
|
||||
# Options are available in the following format:
|
||||
# https://github.com/nixos/nixpkgs/blob/master/nixos/lib/make-options-doc/default.nix
|
||||
#
|
||||
# ```json
|
||||
# {
|
||||
# ...
|
||||
# "fileSystems.<name>.options": {
|
||||
# "declarations": ["nixos/modules/tasks/filesystems.nix"],
|
||||
# "default": {
|
||||
# "_type": "literalExpression",
|
||||
# "text": "[\n \"defaults\"\n]"
|
||||
# },
|
||||
# "description": "Options used to mount the file system.",
|
||||
# "example": {
|
||||
# "_type": "literalExpression",
|
||||
# "text": "[\n \"data=journal\"\n]"
|
||||
# },
|
||||
# "loc": ["fileSystems", "<name>", "options"],
|
||||
# "readOnly": false,
|
||||
# "type": "non-empty (list of string (with check: non-empty))"
|
||||
# "relatedPackages": "- [`pkgs.tmux`](\n https://search.nixos.org/packages?show=tmux&sort=relevance&query=tmux\n )\n",
|
||||
# }
|
||||
# }
|
||||
# ```
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Get environment variables
|
||||
CLAN_CORE = os.getenv("CLAN_CORE")
|
||||
CLAN_MODULES = os.environ.get("CLAN_MODULES")
|
||||
CLAN_MODULES_READMES = os.environ.get("CLAN_MODULES_READMES")
|
||||
|
||||
OUT = os.environ.get("out")
|
||||
|
||||
|
||||
def sanitize(text: str) -> str:
|
||||
return text.replace(">", "\\>")
|
||||
|
||||
|
||||
def replace_store_path(text: str) -> tuple[str, str]:
|
||||
res = text
|
||||
if text.startswith("/nix/store/"):
|
||||
res = "https://git.clan.lol/clan/clan-core/src/branch/main/" + str(
|
||||
Path(*Path(text).parts[4:])
|
||||
)
|
||||
name = Path(res).name
|
||||
return (res, name)
|
||||
|
||||
|
||||
def render_option_header(name: str) -> str:
|
||||
return f"# {name}\n"
|
||||
|
||||
|
||||
def join_lines_with_indentation(lines: list[str], indent: int = 4) -> str:
|
||||
"""
|
||||
Joins multiple lines with a specified number of whitespace characters as indentation.
|
||||
|
||||
Args:
|
||||
lines (list of str): The lines of text to join.
|
||||
indent (int): The number of whitespace characters to use as indentation for each line.
|
||||
|
||||
Returns:
|
||||
str: The indented and concatenated string.
|
||||
"""
|
||||
# Create the indentation string (e.g., four spaces)
|
||||
indent_str = " " * indent
|
||||
# Join each line with the indentation added at the beginning
|
||||
return "\n".join(indent_str + line for line in lines)
|
||||
|
||||
|
||||
def render_option(name: str, option: dict[str, Any], level: int = 3) -> str:
|
||||
read_only = option.get("readOnly")
|
||||
|
||||
res = f"""
|
||||
{"#" * level} {sanitize(name)}
|
||||
{"Readonly" if read_only else ""}
|
||||
{option.get("description", "No description available.")}
|
||||
|
||||
**Type**: `{option["type"]}`
|
||||
|
||||
"""
|
||||
if option.get("default"):
|
||||
res += f"""
|
||||
**Default**:
|
||||
|
||||
```nix
|
||||
{option["default"]["text"] if option.get("default") else "No default set."}
|
||||
```
|
||||
"""
|
||||
example = option.get("example", {}).get("text")
|
||||
if example:
|
||||
example_indented = join_lines_with_indentation(example.split("\n"))
|
||||
res += f"""
|
||||
|
||||
???+ example
|
||||
|
||||
```nix
|
||||
{example_indented}
|
||||
```
|
||||
"""
|
||||
if option.get("relatedPackages"):
|
||||
res += f"""
|
||||
### Related Packages
|
||||
|
||||
{option["relatedPackages"]}
|
||||
"""
|
||||
|
||||
decls = option.get("declarations", [])
|
||||
source_path, name = replace_store_path(decls[0])
|
||||
print(source_path, name)
|
||||
res += f"""
|
||||
:simple-git: [{name}]({source_path})
|
||||
"""
|
||||
res += "\n"
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def module_header(module_name: str) -> str:
|
||||
return f"# {module_name}\n"
|
||||
|
||||
|
||||
def module_usage(module_name: str) -> str:
|
||||
return f"""## Usage
|
||||
|
||||
To use this module, import it like this:
|
||||
|
||||
```nix
|
||||
{{config, lib, inputs, ...}}: {{
|
||||
imports = [ inputs.clan-core.clanModules.{module_name} ];
|
||||
# ...
|
||||
}}
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
clan_core_descr = """ClanCore delivers all the essential features for every clan.
|
||||
It's always included in your setup, and you can customize your clan's behavior with the configuration [options](#module-options) provided below.
|
||||
|
||||
"""
|
||||
|
||||
options_head = "\n## Module Options\n"
|
||||
|
||||
|
||||
def produce_clan_core_docs() -> None:
|
||||
if not CLAN_CORE:
|
||||
raise ValueError(
|
||||
f"Environment variables are not set correctly: $CLAN_CORE={CLAN_CORE}"
|
||||
)
|
||||
|
||||
if not OUT:
|
||||
raise ValueError(f"Environment variables are not set correctly: $out={OUT}")
|
||||
|
||||
# A mapping of output file to content
|
||||
core_outputs: dict[str, str] = {}
|
||||
with open(CLAN_CORE) as f:
|
||||
options: dict[str, dict[str, Any]] = json.load(f)
|
||||
module_name = "clan-core"
|
||||
for option_name, info in options.items():
|
||||
outfile = f"{module_name}/index.md"
|
||||
|
||||
# Create separate files for nested options
|
||||
if len(option_name.split(".")) <= 2:
|
||||
# i.e. clan-core.clanDir
|
||||
output = core_outputs.get(
|
||||
outfile,
|
||||
module_header(module_name) + clan_core_descr + options_head,
|
||||
)
|
||||
output += render_option(option_name, info)
|
||||
# Update the content
|
||||
core_outputs[outfile] = output
|
||||
else:
|
||||
# Clan sub-options
|
||||
[_, sub] = option_name.split(".")[0:2]
|
||||
outfile = f"{module_name}/{sub}.md"
|
||||
# Get the content or write the header
|
||||
output = core_outputs.get(outfile, render_option_header(sub))
|
||||
output += render_option(option_name, info)
|
||||
# Update the content
|
||||
core_outputs[outfile] = output
|
||||
|
||||
for outfile, output in core_outputs.items():
|
||||
(Path(OUT) / outfile).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(Path(OUT) / outfile, "w") as of:
|
||||
of.write(output)
|
||||
|
||||
|
||||
def produce_clan_modules_docs() -> None:
|
||||
if not CLAN_MODULES:
|
||||
raise ValueError(
|
||||
f"Environment variables are not set correctly: $CLAN_MODULES={CLAN_MODULES}"
|
||||
)
|
||||
if not CLAN_MODULES_READMES:
|
||||
raise ValueError(
|
||||
f"Environment variables are not set correctly: $CLAN_MODULES_READMES={CLAN_MODULES_READMES}"
|
||||
)
|
||||
|
||||
if not OUT:
|
||||
raise ValueError(f"Environment variables are not set correctly: $out={OUT}")
|
||||
|
||||
with open(CLAN_MODULES) as f:
|
||||
links: dict[str, str] = json.load(f)
|
||||
|
||||
with open(CLAN_MODULES_READMES) as readme:
|
||||
readme_map: dict[str, str] = json.load(readme)
|
||||
|
||||
# {'borgbackup': '/nix/store/hi17dwgy7963ddd4ijh81fv0c9sbh8sw-options.json', ... }
|
||||
for module_name, options_file in links.items():
|
||||
with open(Path(options_file) / "share/doc/nixos/options.json") as f:
|
||||
options: dict[str, dict[str, Any]] = json.load(f)
|
||||
print(f"Rendering options for {module_name}...")
|
||||
output = module_header(module_name)
|
||||
|
||||
if readme_map.get(module_name, None):
|
||||
output += f"{readme_map[module_name]}\n"
|
||||
|
||||
output += module_usage(module_name)
|
||||
|
||||
output += options_head if len(options.items()) else ""
|
||||
for option_name, info in options.items():
|
||||
output += render_option(option_name, info)
|
||||
|
||||
outfile = Path(OUT) / f"clanModules/{module_name}.md"
|
||||
outfile.parent.mkdir(
|
||||
parents=True,
|
||||
exist_ok=True,
|
||||
)
|
||||
with open(outfile, "w") as of:
|
||||
of.write(output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
produce_clan_core_docs()
|
||||
produce_clan_modules_docs()
|
@ -1,31 +0,0 @@
|
||||
{
|
||||
docs,
|
||||
pkgs,
|
||||
module-docs,
|
||||
clan-cli-docs,
|
||||
asciinema-player-js,
|
||||
asciinema-player-css,
|
||||
roboto,
|
||||
fira-code,
|
||||
...
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
inputsFrom = [ docs ];
|
||||
shellHook = ''
|
||||
mkdir -p ./site/reference/cli
|
||||
cp -af ${module-docs}/* ./site/reference/
|
||||
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
||||
chmod +w ./site/reference/*
|
||||
|
||||
echo "Generated API documentation in './site/reference/' "
|
||||
|
||||
mkdir -p ./site/static/asciinema-player
|
||||
|
||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||
|
||||
# Link to fonts
|
||||
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||
ln -snf ${fira-code}/share/fonts/truetype/FiraCode-VF.ttf ./site/static/
|
||||
'';
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
<meta property="og:title" content="Clan - Documentation, Blog & Getting Started Guide" />
|
||||
<meta property="og:description" content="Documentation for Clan. The peer-to-peer machine deployment framework." />
|
||||
<meta property="og:image" content="https://clan.lol/static/dark-favicon/128x128.png" />
|
||||
<meta property="og:url" content="https://docs.clan.lol" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="Clan" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
|
||||
{% endblock %}
|
135
docs/quickstart.md
Normal file
135
docs/quickstart.md
Normal file
@ -0,0 +1,135 @@
|
||||
# Initializing a New Clan Project
|
||||
|
||||
## Create a new flake
|
||||
|
||||
1. To start a new project, execute the following command to add the clan cli to your shell:
|
||||
|
||||
```shellSession
|
||||
$ nix shell git+https://git.clan.lol/clan/clan-core
|
||||
```
|
||||
|
||||
2. Then use the following commands to initialize a new clan-flake:
|
||||
|
||||
```shellSession
|
||||
$ clan flake create my-clan
|
||||
```
|
||||
|
||||
This action will generate two primary files: `flake.nix` and `.clan-flake`.
|
||||
|
||||
```shellSession
|
||||
$ ls -la
|
||||
drwx------ joerg users 5 B a minute ago ./
|
||||
drwxrwxrwt root root 139 B 12 seconds ago ../
|
||||
.rw-r--r-- joerg users 77 B a minute ago .clan-flake
|
||||
.rw-r--r-- joerg users 4.8 KB a minute ago flake.lock
|
||||
.rw-r--r-- joerg users 242 B a minute ago flake.nix
|
||||
```
|
||||
|
||||
### Understanding the .clan-flake Marker File
|
||||
|
||||
The `.clan-flake` marker file serves an optional purpose: it helps the `clan-cli` utility locate the project's root directory.
|
||||
If `.clan-flake` is missing, `clan-cli` will instead search for other indicators like `.git`, `.hg`, `.svn`, or `flake.nix` to identify the project root.
|
||||
|
||||
## What's next
|
||||
|
||||
After creating your flake, you can check out how to add [new machines](./machines.md)
|
||||
|
||||
---
|
||||
|
||||
# Migrating Existing NixOS Configuration Flake
|
||||
|
||||
Absolutely, let's break down the migration step by step, explaining each action in detail:
|
||||
|
||||
#### Before You Begin
|
||||
|
||||
1. **Backup Your Current Configuration**: Always start by making a backup of your current NixOS configuration to ensure you can revert if needed.
|
||||
|
||||
```shellSession
|
||||
$ cp -r /etc/nixos ~/nixos-backup
|
||||
```
|
||||
|
||||
2. **Update Flake Inputs**: Add a new input for the `clan-core` dependency:
|
||||
|
||||
```nix
|
||||
inputs.clan-core = {
|
||||
url = "git+https://git.clan.lol/clan/clan-core";
|
||||
# Don't do this if your machines are on nixpkgs stable.
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
```
|
||||
|
||||
- `url`: Specifies the Git repository URL for Clan Core.
|
||||
- `inputs.nixpkgs.follows`: Tells Nix to use the same `nixpkgs` input as your main input (in this case, it follows `nixpkgs`).
|
||||
|
||||
3. **Update Outputs**: Then modify the `outputs` section of your `flake.nix` to adapt to Clan Core's new provisioning method. The key changes are as follows:
|
||||
|
||||
Add `clan-core` to the output
|
||||
|
||||
```diff
|
||||
- outputs = { self, nixpkgs, }:
|
||||
+ outputs = { self, nixpkgs, clan-core }:
|
||||
```
|
||||
|
||||
Previous configuration:
|
||||
|
||||
```nix
|
||||
{
|
||||
nixosConfigurations.example-desktop = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
./configuration.nix
|
||||
];
|
||||
[...]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
After change:
|
||||
|
||||
```nix
|
||||
let clan = clan-core.lib.buildClan {
|
||||
# this needs to point at the repository root
|
||||
directory = self;
|
||||
specialArgs = {};
|
||||
clanName = "NEEDS_TO_BE_UNIQUE"; # TODO: Changeme
|
||||
machines = {
|
||||
example-desktop = {
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
imports = [
|
||||
./configuration.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
in { inherit (clan) nixosConfigurations clanInternals; }
|
||||
```
|
||||
|
||||
- `nixosConfigurations`: Defines NixOS configurations, using Clan Core’s `buildClan` function to manage the machines.
|
||||
- Inside `machines`, a new machine configuration is defined (in this case, `example-desktop`).
|
||||
- Inside `example-desktop` which is the target machine hostname, `nixpkgs.hostPlatform` specifies the host platform as `x86_64-linux`.
|
||||
- `clanInternals`: Is required to enable evaluation of the secret generation/upload script on every architecture
|
||||
- `clanName`: Is required and needs to be globally unique, as else we have a cLAN name clash
|
||||
|
||||
4. **Rebuild and Switch**: Rebuild your NixOS configuration using the updated flake:
|
||||
|
||||
```shellSession
|
||||
$ sudo nixos-rebuild switch --flake .
|
||||
```
|
||||
|
||||
- This command rebuilds and switches to the new configuration. Make sure to include the `--flake .` argument to use the current directory as the flake source.
|
||||
|
||||
5. **Test Configuration**: Before rebooting, verify that your new configuration builds without errors or warnings.
|
||||
|
||||
6. **Reboot**: If everything is fine, you can reboot your system to apply the changes:
|
||||
|
||||
```shellSession
|
||||
$ sudo reboot
|
||||
```
|
||||
|
||||
7. **Verify**: After the reboot, confirm that your system is running with the new configuration, and all services and applications are functioning as expected.
|
||||
|
||||
By following these steps, you've successfully migrated your NixOS Flake configuration to include the `clan-core` input and adapted the `outputs` section to work with Clan Core's new machine provisioning method.
|
||||
|
||||
## What's next
|
||||
|
||||
After creating your flake, you can check out how to add [new machines](./machines.md)
|
173
docs/secrets-management.md
Normal file
173
docs/secrets-management.md
Normal file
@ -0,0 +1,173 @@
|
||||
# Managing Secrets with Clan
|
||||
|
||||
Clan enables encryption of secrets within a Clan flake, ensuring secure sharing among users.
|
||||
This documentation will guide you through managing secrets with the Clan CLI,
|
||||
which utilizes the [sops](https://github.com/getsops/sops) format and
|
||||
integrates with [sops-nix](https://github.com/Mic92/sops-nix) on NixOS machines.
|
||||
|
||||
## 1. Generating Keys and Creating Secrets
|
||||
|
||||
To begin, generate a key pair:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets key generate
|
||||
```
|
||||
|
||||
**Output**:
|
||||
|
||||
```
|
||||
Public key: age1wkth7uhpkl555g40t8hjsysr20drq286netu8zptw50lmqz7j95sw2t3l7
|
||||
Generated age private key at '/home/joerg/.config/sops/age/keys.txt' for your user.
|
||||
Generated age private key at '/home/joerg/.config/sops/age/keys.txt' for your user. Please back it up on a secure location or you will lose access to your secrets.
|
||||
Also add your age public key to the repository with 'clan secrets users add youruser age1wkth7uhpkl555g40t8hjsysr20drq286netu8zptw50lmqz7j95sw2t3l7' (replace you
|
||||
user with your user name)
|
||||
```
|
||||
|
||||
⚠️ **Important**: Backup the generated private key securely, or risk losing access to your secrets.
|
||||
|
||||
Next, add your public key to the Clan flake repository:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets users add <your_username> <your_public_key>
|
||||
```
|
||||
|
||||
Doing so creates this structure in your Clan flake:
|
||||
|
||||
```
|
||||
sops/
|
||||
└── users/
|
||||
└── <your_username>/
|
||||
└── key.json
|
||||
```
|
||||
|
||||
Now, to set your first secret:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets set mysecret
|
||||
Paste your secret:
|
||||
```
|
||||
|
||||
Note: As you type your secret, keypresses won't be displayed. Press Enter to save the secret.
|
||||
|
||||
Retrieve the stored secret:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets get mysecret
|
||||
```
|
||||
|
||||
And list all secrets like this:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets list
|
||||
```
|
||||
|
||||
Secrets in the repository follow this structure:
|
||||
|
||||
```
|
||||
sops/
|
||||
├── secrets/
|
||||
│ └── <secret_name>/
|
||||
│ ├── secret
|
||||
│ └── users/
|
||||
│ └── <your_username>/
|
||||
```
|
||||
|
||||
The content of the secret is stored encrypted inside the `secret` file under `mysecret`.
|
||||
By default, secrets are encrypted with your key to ensure readability.
|
||||
|
||||
## 2. Adding Machine Keys
|
||||
|
||||
New machines in Clan come with age keys stored in `./sops/machines/<machine_name>`. To list these machines:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets machines list
|
||||
```
|
||||
|
||||
For existing machines, add their keys:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets machines add <machine_name> <age_key>
|
||||
```
|
||||
|
||||
To fetch an age key from an SSH host key:
|
||||
|
||||
```shellSession
|
||||
$ ssh-keyscan <domain_name> | nix shell nixpkgs#ssh-to-age -c ssh-to-age
|
||||
```
|
||||
|
||||
## 3. Assigning Access
|
||||
|
||||
By default, secrets are encrypted for your key. To specify which users and machines can access a secret:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets set --machine <machine1> --machine <machine2> --user <user1> --user <user2> <secret_name>
|
||||
```
|
||||
|
||||
You can add machines/users to existing secrets without modifying the secret:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets machines add-secret <machine_name> <secret_name>
|
||||
```
|
||||
|
||||
## 4. Utilizing Groups
|
||||
|
||||
For convenience, Clan CLI allows group creation to simplify access management. Here's how:
|
||||
|
||||
1. **Creating Groups**:
|
||||
|
||||
Assign users to a new group, e.g., `admins`:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets groups add admins <username>
|
||||
```
|
||||
|
||||
2. **Listing Groups**:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets groups list
|
||||
```
|
||||
|
||||
3. **Assigning Secrets to Groups**:
|
||||
|
||||
```shellSession
|
||||
$ clan secrets groups add-secret <group_name> <secret_name>
|
||||
```
|
||||
|
||||
# NixOS integration
|
||||
|
||||
A NixOS machine will automatically import all secrets that are encrypted for the
|
||||
current machine. At runtime it will use the host key to decrypt all secrets into
|
||||
a in-memory, non-persistent filesystem using
|
||||
[sops-nix](https://github.com/Mic92/sops-nix). In your nixos configuration you
|
||||
can get a path to secrets like this `config.sops.secrets.<name>.path`. Example:
|
||||
|
||||
```nix
|
||||
{ config, ...}: {
|
||||
sops.secrets.my-password.neededForUsers = true;
|
||||
|
||||
users.users.mic92 = {
|
||||
isNormalUser = true;
|
||||
passwordFile = config.sops.secrets.my-password.path;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See the [readme](https://github.com/Mic92/sops-nix) of sops-nix for more
|
||||
examples.
|
||||
|
||||
# Importing existing sops-based keys / sops-nix
|
||||
|
||||
`clan secrets` stores each secrets in a single file, whereas [sops](https://github.com/Mic92/sops-nix)
|
||||
commonly allows to put all secrets in a yaml or json documents.
|
||||
|
||||
If you already happend to use sops-nix, you can migrate by using the `clan secrets import-sops` command by importing these documents:
|
||||
|
||||
```shellSession
|
||||
% clan secrets import-sops --prefix matchbox- --group admins --machine matchbox nixos/matchbox/secrets/secrets.yaml
|
||||
```
|
||||
|
||||
This will create secrets for each secret found in `nixos/matchbox/secrets/secrets.yaml` in a ./sops folder of your repository.
|
||||
Each member of the group `admins` will be able
|
||||
|
||||
Since our clan secret module will auto-import secrets that are encrypted for a particular nixos machine,
|
||||
you can now remove `sops.secrets.<secrets> = { };` unless you need to specify more options for the secret like owner/group of the secret file.
|
@ -1,26 +0,0 @@
|
||||
authors:
|
||||
DavHau:
|
||||
name: "DavHau"
|
||||
description: "Core Developer"
|
||||
avatar: "https://clan.lol/static/profiles/davhau.jpg"
|
||||
url: "https://DavHau.com"
|
||||
Lassulus:
|
||||
name: "Lassulus"
|
||||
description: "Core Developer"
|
||||
avatar: "https://clan.lol/static/profiles/lassulus.jpg"
|
||||
url: "https://http://lassul.us/"
|
||||
Mic92:
|
||||
name: "Mic92"
|
||||
description: "Core Developer"
|
||||
avatar: "https://clan.lol/static/profiles/mic92.jpg"
|
||||
url: "https://thalheim.io"
|
||||
W:
|
||||
name: "W"
|
||||
description: "Founder of Clan"
|
||||
avatar: "https://clan.lol/static/profiles/w_profile.webp"
|
||||
url: ""
|
||||
Qubasa:
|
||||
name: "Qubasa"
|
||||
description: "Core Developer"
|
||||
avatar: "https://clan.lol/static/profiles/qubasa.png"
|
||||
url: "https://github.com/Qubasa"
|
@ -1,2 +0,0 @@
|
||||
# Blog
|
||||
|
@ -1,72 +0,0 @@
|
||||
---
|
||||
title: "Introducing Clan: Full-Stack Computing Redefined"
|
||||
description: "Introducing Clan, a new model for a decentralized network, designed to provide families, smaller groups, and small businesses a platform that’s private, secure, and user-friendly."
|
||||
authors:
|
||||
- W
|
||||
- Qubasa
|
||||
date: 2024-03-19
|
||||
---
|
||||
|
||||
|
||||
In a digital age where users are guided increasingly toward submission and dependence, Clan reclaims computing and networking from the ground up.
|
||||
|
||||
Clan enables users to build any system from a git repository, automate secret handling, and join devices in a secure darknet. This control extends beyond applications to communication protocols and the operating system itself, putting you fully in charge of your own digital environment.
|
||||
|
||||
## Why We're Building Clan
|
||||
|
||||
Our mission is simple: to restore fun, freedom, and functionality to computing as an open source project. We believe in building tools that empower users, foster innovation, and challenge the limitations imposed by outdated paradigms. Clan, in its essence, is an open source endeavor; it's our contribution to a future where technology serves humanity, not the other way around.
|
||||
|
||||
## How Clan Changes the Game
|
||||
Clan embodies a new philosophy in system, application, and network design. It enables seamless, secure communication across devices, simplifies software distribution and updates, and offers both public and private network configurations. Here are some of the ways it accomplishes this:
|
||||
|
||||
- **Nix as a Foundation:** Imagine a safety net for your computer's operating system, one that lets you make changes or updates without the fear of causing a crash or losing data. Nix simplifies the complexities of system design, ensuring that updates are safe and systems are more reliable.
|
||||
|
||||
- **Simplified System Deployment:** Building and managing a computer system, from the operating system to the software you use, often feels like putting together a complex puzzle. With Clan, the puzzle pieces are replaced by a set of building blocks. Leveraging the power of Nix and Clan's innovative toolkit, anyone from tech-savvy administrators to everyday users can create and maintain what we call "full-stack systems" (everything your computer needs to run smoothly).
|
||||
|
||||
- **A Leap in Connectivity:** Imagine if you could create private, secure pathways between your devices, bypassing the noisy and often insecure internet. Clan makes this possible through something called "overlay networks." These networks are like private tunnels, allowing your devices to talk to each other securely and directly. With Clan's built-in overlay networks and automatically configured services, connecting your devices becomes seamless, secure, and hassle-free.
|
||||
|
||||
- **Security Through Separation:** Clan employs sandboxing and virtual machines, a technology that runs code in isolated environments - so even if you explore new Clans, your system remains protected from potential threats.
|
||||
|
||||
- **Reliable:** With Clan, your data and services are preserved for the long haul. We focus on self-hosted backups and integration with the [Fediverse](https://de.wikipedia.org/wiki/Fediverse), a network of interconnected, independent online communities, so your digital life remains uninterrupted and under your control.
|
||||
|
||||
|
||||
## A Glimpse at Clan's Features
|
||||
|
||||
- **Social Scaling:** Choose between creating a private sanctuary for your closest contacts, a dynamic space for a self-contained community, or embracing the open web with public Clans anyone can join.
|
||||
|
||||
{{ video(name="show_join.webm")}}
|
||||
|
||||
- **Seamless VM Integration:** Applications running in virtual machines can appear and behave as if they're part of your main operating system — a blend of power and simplicity.
|
||||
|
||||
{{ video(name="show_run.webm")}}
|
||||
|
||||
- **Robust Backup Management:** Keep your data safe _forever_ - never worry about cloud services disappearing in 10 years.
|
||||
|
||||
{{ asciinema(name="backups.cast") }}
|
||||
|
||||
- **Intuitive Secret Management:** Clan simplifies digital security by automating the creation and management of encryption keys and passwords for your services.
|
||||
|
||||
{{ asciinema(name="secrets.cast") }}
|
||||
|
||||
- **Remote Install:** Set up and manage Clan systems anywhere in the world with just a QR scan or SSH access, making remote installations as easy as snapping a photo or sharing a link.
|
||||
|
||||
{{ asciinema(name="nixos-install.cast") }}
|
||||
|
||||
|
||||
## Who Stands to Benefit?
|
||||
|
||||
Clan is for anyone and everyone who believes in the power of open source technology to connect, empower, and protect. From system administrators to less tech-savvy individuals, small business owners to privacy-conscious users, Clan offers something for everyone — a way to reclaim control and redefine how we interact with technology.
|
||||
|
||||
## Join the Revolution
|
||||
|
||||
Ready to control your digital world? Clan is more than a tool—it's a movement. Secure your data, manage your systems easily, or connect with others how you like. Start with Clan for a better digital future.
|
||||
|
||||
Connect with us on our [Matrix channel at clan.lol](https://matrix.to/#/#clan:lassul.us) or through our IRC bridges (coming soon).
|
||||
|
||||
Want to see the code? Check it out [on our Gitea](https://git.clan.lol/clan/clan-core) or [on GitHub](https://github.com/clan-lol/clan-core).
|
||||
|
||||
Or follow our [RSS feed](https://docs.clan.lol/feed_rss_created.xml)!
|
||||
|
||||
Join us and be part of changing technology for the better, together.
|
||||
|
||||
|
@ -1,194 +0,0 @@
|
||||
---
|
||||
title: "Dev Report: Introducing the NixOS to JSON Schema Converter"
|
||||
description: "Discover our new library designed to extract JSON schema interfaces from NixOS modules, streamlining frontend development"
|
||||
authors:
|
||||
- DavHau
|
||||
date: 2024-05-25
|
||||
slug: jsonschema-converter
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
We’ve developed a new library designed to extract interfaces from NixOS modules and convert them into JSON schemas, paving the way for effortless GUI generation. This blog post outlines the motivations behind this development, demonstrates the capabilities of the library, and guides you through leveraging it to create GUIs seamlessly.
|
||||
|
||||
## Motivation
|
||||
|
||||
In recent months, our team has been exploring various graphical user interfaces (GUIs) to streamline NixOS machine configuration. While our opinionated Clan modules simplify NixOS configurations, there's a need to configure these modules from diverse frontends, such as:
|
||||
|
||||
- Command-line interfaces (CLIs)
|
||||
- Web-based UIs
|
||||
- Desktop applications
|
||||
- Mobile applications
|
||||
- Large Language Models (LLMs)
|
||||
|
||||
Given this need, a universal format like JSON is a natural choice. It is already possible as of now, to import json based NixOS configurations, as illustrated below:
|
||||
|
||||
`configuration.json`:
|
||||
```json
|
||||
{ "networking": { "hostName": "my-machine" } }
|
||||
```
|
||||
|
||||
This configuration can be then imported inside a classic NixOS config:
|
||||
```nix
|
||||
{config, lib, pkgs, ...}: {
|
||||
imports = [
|
||||
(lib.importJSON ./configuration.json)
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
This straightforward approach allows us to build a frontend that generates JSON, enabling the configuration of NixOS machines. But, two critical questions arise:
|
||||
|
||||
1. How does the frontend learn about existing configuration options?
|
||||
2. How can it verify user input without running Nix?
|
||||
|
||||
Introducing [JSON schema](https://json-schema.org/), a widely supported standard that defines interfaces in JSON and validates input against them.
|
||||
|
||||
Example schema for `networking.hostName`:
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"networking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hostName": {
|
||||
"type": "string",
|
||||
"pattern": "^$|^[a-z0-9]([a-z0-9_-]{0,61}[a-z0-9])?$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Client-Side Input Validation
|
||||
|
||||
Validating input against JSON schemas is both efficient and well-supported across numerous programming languages. Using JSON schema validators, you can accurately check configurations like our `configuration.json`.
|
||||
|
||||
Validation example:
|
||||
|
||||
```shell
|
||||
$ nix-shell -p check-jsonschema
|
||||
$ jsonschema -o pretty ./schema.json -i ./configuration.json
|
||||
===[SUCCESS]===(./configuration.json)===
|
||||
```
|
||||
|
||||
In case of invalid input, schema validators provide explicit error messages:
|
||||
|
||||
```shell
|
||||
$ echo '{ "networking": { "hostName": "my/machine" } }' > configuration.json
|
||||
$ jsonschema -o pretty ./schema.json -i ./configuration.json
|
||||
===[ValidationError]===(./configuration.json)===
|
||||
|
||||
'my/machine' does not match '^$|^[a-z0-9]([a-z0-9_-]{0,61}[a-z0-9])?$'
|
||||
|
||||
Failed validating 'pattern' in schema['properties']['networking']['properties']['hostName']:
|
||||
{'pattern': '^$|^[a-z0-9]([a-z0-9_-]{0,61}[a-z0-9])?$',
|
||||
'type': 'string'}
|
||||
|
||||
On instance['networking']['hostName']:
|
||||
'my/machine'
|
||||
```
|
||||
|
||||
## Automatic GUI Generation
|
||||
|
||||
Certain libraries facilitate straightforward GUI generation from JSON schemas. For instance, the [react-jsonschema-form playground](https://rjsf-team.github.io/react-jsonschema-form/) auto-generates a form for any given schema.
|
||||
|
||||
## NixOS Module to JSON Schema Converter
|
||||
|
||||
To enable the development of responsive frontends, our library allows the extraction of interfaces from NixOS modules to JSON schemas. Open-sourced for community collaboration, this library supports building sophisticated user interfaces for NixOS.
|
||||
|
||||
Here’s a preview of our library's functions exposed through the [clan-core](https://git.clan.lol/clan/clan-core) flake:
|
||||
|
||||
- `lib.jsonschema.parseModule` - Generates a schema for a NixOS module.
|
||||
- `lib.jsonschema.parseOption` - Generates a schema for a single NixOS option.
|
||||
- `lib.jsonschema.parseOptions` - Generates a schema from an attrset of NixOS options.
|
||||
|
||||
Example:
|
||||
`module.nix`:
|
||||
```nix
|
||||
{lib, config, pkgs, ...}: {
|
||||
# a simple service with two options
|
||||
options.services.example-web-service = {
|
||||
enable = lib.mkEnableOption "Example web service";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "Port used to serve the content";
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Converted, using the `parseModule` function:
|
||||
```shell
|
||||
$ cd clan-core
|
||||
$ nix eval --json --impure --expr \
|
||||
'(import ./lib/jsonschema {}).parseModule ./module.nix' | jq | head
|
||||
{
|
||||
"properties": {
|
||||
"services": {
|
||||
"properties": {
|
||||
"example-web-service": {
|
||||
"properties": {
|
||||
"enable": {
|
||||
"default": false,
|
||||
"description": "Whether to enable Example web service.",
|
||||
"examples": [
|
||||
...
|
||||
```
|
||||
|
||||
This utility can also generate interfaces for existing NixOS modules or options.
|
||||
|
||||
## GUI for NGINX in Under a Minute
|
||||
|
||||
Creating a prototype GUI for the NGINX module using our library and [react-jsonschema-form playground](https://rjsf-team.github.io/react-jsonschema-form/) can be done quickly:
|
||||
|
||||
1. Export all NGINX options into a JSON schema using a Nix expression:
|
||||
|
||||
```nix
|
||||
# export.nix
|
||||
let
|
||||
pkgs = import <nixpkgs> {};
|
||||
clan-core = builtins.getFlake "git+https://git.clan.lol/clan/clan-core";
|
||||
options = (pkgs.nixos {}).options.services.nginx;
|
||||
in
|
||||
clan-core.lib.jsonschema.parseOption options
|
||||
```
|
||||
|
||||
2. Write the schema into a file:
|
||||
```shell
|
||||
$ nix eval --json -f ./export.nix | jq > nginx.json
|
||||
```
|
||||
|
||||
3. Open the [react-jsonschema-form playground](https://rjsf-team.github.io/react-jsonschema-form/), select `Blank` and paste the `nginx.json` contents.
|
||||
|
||||
This provides a quick look at a potential GUI (screenshot is cropped).
|
||||
|
||||
![Image title](https://clan.lol/static/blog-post-jsonschema/nginx-gui.jpg)
|
||||
|
||||
## Limitations
|
||||
|
||||
### Laziness
|
||||
|
||||
JSON schema mandates the declaration of all required fields upfront, which might be configured implicitly or remain unused. For instance, `services.nginx.virtualHosts.<name>.sslCertificate` must be specified even if SSL isn’t enabled.
|
||||
|
||||
### Limited Types
|
||||
|
||||
Certain NixOS module types, like `types.functionTo` and `types.package`, do not map straightforwardly to JSON. For full compatibility, adjustments to NixOS modules might be necessary, such as substituting `listOf package` with `listOf str`.
|
||||
|
||||
### Parsing NixOS Modules
|
||||
|
||||
Currently, our converter relies on the `options` attribute of evaluated NixOS modules, extracting information from the `type.name` attribute, which is suboptimal. Enhanced introspection capabilities within the NixOS module system would be beneficial.
|
||||
|
||||
## Future Prospects
|
||||
|
||||
We hope these experiments inspire the community, encourage contributions and further development in this space. Share your ideas and contributions through our issue tracker or matrix channel!
|
||||
|
||||
## Links
|
||||
|
||||
- [Comments on NixOS Discourse](https://discourse.nixos.org/t/introducing-the-nixos-to-json-schema-converter/45948)
|
||||
- [Source Code of the JSON Schema Library](https://git.clan.lol/clan/clan-core/src/branch/main/lib/jsonschema)
|
||||
- [Our Issue Tracker](https://git.clan.lol/clan/clan-core/issues)
|
||||
- [Our Matrix Channel](https://matrix.to/#/#clan:lassul.us)
|
||||
- [react-jsonschema-form Playground](https://rjsf-team.github.io/react-jsonschema-form/)
|
@ -1,13 +0,0 @@
|
||||
---
|
||||
title: "New documentation site and weekly new meetup"
|
||||
authors:
|
||||
- Lassulus
|
||||
- Mic92
|
||||
date: 2024-04-16
|
||||
---
|
||||
|
||||
Last week, we added a new documentation hub for clan at [docs.clan.lol](https://docs.clan.lol).
|
||||
We are still working on improving the installation procedures, so stay tuned.
|
||||
We now have weekly office hours where people are invited to hangout and ask questions.
|
||||
They are every Wednesday 15:30 UTC (17:30 CEST) in our [jitsi](https://jitsi.lassul.us/clan.lol).
|
||||
Otherwise drop by in our [matrix channel](https://matrix.to/#/#clan:lassul.us).
|
@ -1 +0,0 @@
|
||||
../../CONTRIBUTING.md
|
@ -1,63 +0,0 @@
|
||||
---
|
||||
title: "Git Based Machine Deployment with Clan-Core"
|
||||
description: ""
|
||||
authors:
|
||||
- Qubasa
|
||||
date: 2024-05-25
|
||||
---
|
||||
## Revolutionizing Server Management
|
||||
|
||||
In the world of server management, countless tools claim to offer seamless deployment of multiple machines. Yet, many fall short, leaving server admins and self-hosting enthusiasts grappling with complexity. Enter the Clan-Core Framework—a groundbreaking all in one solution designed to transform decentralized self-hosting into an effortless and scalable endeavor.
|
||||
|
||||
### The Power of Clan-Core
|
||||
|
||||
Imagine having the power to manage your servers with unparalleled ease, scaling your IT infrastructure like never before. Clan-Core empowers you to do just that. At its core, Clan-Core leverages a single Git repository to define everything about your machines. This central repository utilizes Nix or JSON files to specify configurations, including disk formatting, ensuring a streamlined and unified approach.
|
||||
|
||||
### Simplified Deployment Process
|
||||
|
||||
With Clan-Core, the cumbersome task of bootstrapping a specific ISO is a thing of the past. All you need is SSH access to your Linux server. Clan-Core allows you to overwrite any existing Linux distribution live over SSH, eliminating time-consuming setup processes. This capability means you can deploy updates or new configurations swiftly and efficiently, maximizing uptime and minimizing hassle.
|
||||
|
||||
### Secure and Efficient Secret Management
|
||||
|
||||
Security is paramount in server management, and Clan-Core takes it seriously. Passwords and other sensitive information are encrypted within the Git repository, automatically decrypted during deployment. This not only ensures the safety of your secrets but also simplifies their management. Clan-Core supports sharing secrets with other admins, fostering collaboration and maintaining reproducibillity and security without sacrificing convenience.
|
||||
|
||||
### Services as Apps
|
||||
|
||||
Setting up a service can be quite difficult. Many server adjustments need to be made, from setting up a database to adjusting webserver configurations and generating the correct private keys. However, Clan-Core aims to make setting up a service as easy as installing an application. Through Clan-Core's Module system, everything down to secrets can be automatically set up. This transforms the often daunting task of service setup into a smooth, automated process, making it accessible to all.
|
||||
|
||||
### Decentralized Mesh VPN
|
||||
|
||||
Building on these features is a self-configuring decentralized mesh VPN that interconnects all your machines into a private darknet. This ensures that sensitive services, which might have too much attack surface to be hosted on the public internet, can still be made available privately without the need to worry about potential system compromise. By creating a secure, private network, Clan-Core offers an additional layer of protection for your most critical services.
|
||||
|
||||
### Decentralized Domain Name System
|
||||
|
||||
Current DNS implementations are distributed but not truly decentralized. For Clan-Core, we implemented our own truly decentralized DNS module. This module uses simple flooding and caching algorithms to discover available domains inside the darknet. This approach ensures that your internal domain name system is robust, reliable, and independent of external control, enhancing the resilience and security of your infrastructure.
|
||||
|
||||
|
||||
### A New Era of Decentralized Self-Hosting
|
||||
|
||||
Clan-Core is more than just a tool; it's a paradigm shift in server management. By consolidating machine definitions, secrets and network configuration, into a single, secure repository, it transforms how you manage and scale your infrastructure. Whether you're a seasoned server admin or a self-hosting enthusiast, Clan-Core offers a powerful, user-friendly solution to take your capabilities to the next level.
|
||||
|
||||
|
||||
### Key Features of Clan-Core:
|
||||
|
||||
- **Unified Git Repository**: All machine configurations and secrets stored in a single repository.
|
||||
- **Live Overwrites**: Deploy configurations over existing Linux distributions via SSH.
|
||||
- **Automated Service Setup**: Easily set up services with Clan-Core's Module system.
|
||||
- **Decentralized Mesh VPN**: Securely interconnect all machines into a private darknet.
|
||||
- **Decentralized DNS**: Robust, independent DNS using flooding and caching algorithms.
|
||||
- **Automated Secret Management**: Encrypted secrets that are automatically decrypted during deployment.
|
||||
- **Collaboration Support**: Share secrets securely with other admins.
|
||||
|
||||
|
||||
## Clan-Cores Future
|
||||
|
||||
Our vision for Clan-Core extends far beyond being just another deployment tool. Clan-Core is a framework we've developed to achieve something much greater. We want to put the "personal" back into "personal computing." Our goal is for everyday users to fully customize their phones or laptops and create truly private spaces for friends and family.
|
||||
|
||||
Our first major step is to develop a Graphical User Interface (GUI) that makes configuring all this possible. Initial tests have shown that AI can be leveraged as an alternative to traditional GUIs. This paves the way for a future where people can simply talk to their computers, and they will configure themselves according to the users' wishes.
|
||||
|
||||
By adopting Clan, you're not just embracing a tool—you're joining a movement towards a more efficient, secure, and scalable approach to server management. Join us and revolutionize your IT infrastructure today.
|
||||
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user