forked from clan/clan-core
Merge pull request 'Inventory: init first implementation' (#1638) from hsjobeki/clan-core:wip/inventory into main
Reviewed-on: clan/clan-core#1638
This commit is contained in:
commit
25db02368a
@ -3,7 +3,7 @@ let
|
||||
clanDir = config.clan.core.clanDir;
|
||||
machineDir = clanDir + "/machines/";
|
||||
in
|
||||
{
|
||||
lib.warn "This module is deprecated use the service via the inventory interface instead." {
|
||||
imports = [ ../borgbackup ];
|
||||
|
||||
options.clan.borgbackup-static = {
|
||||
|
@ -27,8 +27,55 @@ let
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
|
||||
# Each .nix file in the roles directory is a role
|
||||
# TODO: Helper function to set available roles within module meta.
|
||||
roles =
|
||||
if builtins.pathExists ./roles then
|
||||
lib.pipe ./roles [
|
||||
builtins.readDir
|
||||
(lib.filterAttrs (_n: v: v == "regular"))
|
||||
lib.attrNames
|
||||
(map (fileName: lib.removeSuffix ".nix" fileName))
|
||||
]
|
||||
else
|
||||
null;
|
||||
|
||||
# TODO: make this an interface of every module
|
||||
# Maybe load from readme.md
|
||||
metaInfoOption = lib.mkOption {
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Meta is used to retrieve information about this module.
|
||||
|
||||
- `availableRoles` is a list of roles that can be assigned via the inventory.
|
||||
- `category` is used to group services in the clan marketplace.
|
||||
- `description` is a short description of the service for the clan marketplace.
|
||||
'';
|
||||
default = {
|
||||
description = "Borgbackup is a backup program. Optionally, it supports compression and authenticated encryption.";
|
||||
availableRoles = roles;
|
||||
category = "backup";
|
||||
};
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
description = lib.mkOption { type = lib.types.str; };
|
||||
availableRoles = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); };
|
||||
category = lib.mkOption {
|
||||
description = "A category for the service. This is used to group services in the clan ui";
|
||||
type = lib.types.enum [
|
||||
"backup"
|
||||
"network"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
options.clan.borgbackup.meta = metaInfoOption;
|
||||
|
||||
options.clan.borgbackup.destinations = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
@ -76,7 +123,7 @@ in
|
||||
lib.nameValuePair "borgbackup-job-${dest.name}" {
|
||||
# since borgbackup mounts the system read-only, we need to run in a ExecStartPre script, so we can generate additional files.
|
||||
serviceConfig.ExecStartPre = [
|
||||
(''+${pkgs.writeShellScript "borgbackup-job-${dest.name}-pre-backup-commands" preBackupScript}'')
|
||||
''+${pkgs.writeShellScript "borgbackup-job-${dest.name}-pre-backup-commands" preBackupScript}''
|
||||
];
|
||||
}
|
||||
) cfg.destinations;
|
||||
|
30
clanModules/borgbackup/roles/client.nix
Normal file
30
clanModules/borgbackup/roles/client.nix
Normal file
@ -0,0 +1,30 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
instances = config.clan.inventory.services.borgbackup;
|
||||
# roles = { ${role_name} :: { machines :: [string] } }
|
||||
allServers = lib.foldlAttrs (
|
||||
acc: _instanceName: instanceConfig:
|
||||
acc
|
||||
++ (
|
||||
if builtins.elem machineName instanceConfig.roles.client.machines then
|
||||
instanceConfig.roles.server.machines
|
||||
else
|
||||
[ ]
|
||||
)
|
||||
) [ ] instances;
|
||||
|
||||
inherit (config.clan.core) machineName;
|
||||
in
|
||||
{
|
||||
config.clan.borgbackup.destinations =
|
||||
let
|
||||
|
||||
destinations = builtins.map (serverName: {
|
||||
name = serverName;
|
||||
value = {
|
||||
repo = "borg@${serverName}:/var/lib/borgbackup/${machineName}";
|
||||
};
|
||||
}) allServers;
|
||||
in
|
||||
(builtins.listToAttrs destinations);
|
||||
}
|
45
clanModules/borgbackup/roles/server.nix
Normal file
45
clanModules/borgbackup/roles/server.nix
Normal file
@ -0,0 +1,45 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
clanDir = config.clan.core.clanDir;
|
||||
machineDir = clanDir + "/machines/";
|
||||
inherit (config.clan.core) machineName;
|
||||
|
||||
instances = config.clan.inventory.services.borgbackup;
|
||||
|
||||
# roles = { ${role_name} :: { machines :: [string] } }
|
||||
|
||||
allClients = lib.foldlAttrs (
|
||||
acc: _instanceName: instanceConfig:
|
||||
acc
|
||||
++ (
|
||||
if (builtins.elem machineName instanceConfig.roles.server.machines) then
|
||||
instanceConfig.roles.client.machines
|
||||
else
|
||||
[ ]
|
||||
)
|
||||
) [ ] instances;
|
||||
in
|
||||
{
|
||||
config.services.borgbackup.repos =
|
||||
let
|
||||
borgbackupIpMachinePath = machines: machineDir + machines + "/facts/borgbackup.ssh.pub";
|
||||
machinesMaybeKey = builtins.map (
|
||||
machine:
|
||||
let
|
||||
fullPath = borgbackupIpMachinePath machine;
|
||||
in
|
||||
if builtins.pathExists fullPath then machine else null
|
||||
) allClients;
|
||||
|
||||
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
|
||||
|
||||
hosts = builtins.map (machine: {
|
||||
name = machine;
|
||||
value = {
|
||||
path = "/var/lib/borgbackup/${machine}";
|
||||
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
|
||||
};
|
||||
}) machinesWithKey;
|
||||
in
|
||||
if (builtins.listToAttrs hosts) != [ ] then builtins.listToAttrs hosts else { };
|
||||
}
|
@ -49,7 +49,7 @@ nav:
|
||||
- Mesh VPN: getting-started/mesh-vpn.md
|
||||
- Backup & Restore: getting-started/backups.md
|
||||
- Flake-parts: getting-started/flake-parts.md
|
||||
- Modules:
|
||||
- Reference:
|
||||
- Clan Modules:
|
||||
- reference/clanModules/borgbackup-static.md
|
||||
- reference/clanModules/borgbackup.md
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
clanModulesFileInfo = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModules);
|
||||
clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes);
|
||||
clanModulesMeta = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesMeta);
|
||||
|
||||
# Simply evaluated options (JSON)
|
||||
renderOptions =
|
||||
@ -54,6 +55,7 @@
|
||||
# A file that contains the links to all clanModule docs
|
||||
export CLAN_MODULES=${clanModulesFileInfo}
|
||||
export CLAN_MODULES_READMES=${clanModulesReadmes}
|
||||
export CLAN_MODULES_META=${clanModulesMeta}
|
||||
|
||||
mkdir $out
|
||||
|
||||
|
@ -43,11 +43,16 @@ let
|
||||
module_name: _module: self.lib.modules.getReadme module_name
|
||||
) clanModules;
|
||||
|
||||
clanModulesMeta = builtins.mapAttrs (
|
||||
module_name: _module:
|
||||
(self.lib.evalClanModules [ module_name ]).config.clan.${module_name}.meta or { }
|
||||
) clanModules;
|
||||
|
||||
# clanCore docs
|
||||
clanCoreDocs = (evalDocs (getOptions [ ]).clan.core).optionsJSON;
|
||||
in
|
||||
{
|
||||
inherit clanModulesReadmes;
|
||||
inherit clanModulesReadmes clanModulesMeta;
|
||||
clanCore = clanCoreDocs;
|
||||
clanModules = clanModulesDocs;
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ from typing import Any
|
||||
CLAN_CORE = os.getenv("CLAN_CORE")
|
||||
CLAN_MODULES = os.environ.get("CLAN_MODULES")
|
||||
CLAN_MODULES_READMES = os.environ.get("CLAN_MODULES_READMES")
|
||||
CLAN_MODULES_META = os.environ.get("CLAN_MODULES_META")
|
||||
|
||||
|
||||
OUT = os.environ.get("out")
|
||||
|
||||
@ -76,7 +78,9 @@ def render_option(name: str, option: dict[str, Any], level: int = 3) -> str:
|
||||
|
||||
res = f"""
|
||||
{"#" * level} {sanitize(name)}
|
||||
{"Readonly" if read_only else ""}
|
||||
|
||||
{"**Readonly**" if read_only else ""}
|
||||
|
||||
{option.get("description", "No description available.")}
|
||||
|
||||
**Type**: `{option["type"]}`
|
||||
@ -188,6 +192,35 @@ def produce_clan_core_docs() -> None:
|
||||
of.write(output)
|
||||
|
||||
|
||||
def render_meta(meta: dict[str, Any], module_name: str) -> str:
|
||||
roles = meta.get("availableRoles", None)
|
||||
|
||||
if roles:
|
||||
roles_list = "\n".join([f" - `{r}`" for r in roles])
|
||||
return f"""
|
||||
???+ tip "Inventory (WIP)"
|
||||
|
||||
Predefined roles:
|
||||
|
||||
{roles_list}
|
||||
|
||||
Usage:
|
||||
|
||||
```{{.nix hl_lines="4"}}
|
||||
buildClan {{
|
||||
inventory.services = {{
|
||||
{module_name}.instance_1 = {{
|
||||
roles.{roles[0]}.machines = [ "sara_machine" ];
|
||||
# ...
|
||||
}};
|
||||
}};
|
||||
}}
|
||||
```
|
||||
|
||||
"""
|
||||
return ""
|
||||
|
||||
|
||||
def produce_clan_modules_docs() -> None:
|
||||
if not CLAN_MODULES:
|
||||
raise ValueError(
|
||||
@ -198,6 +231,11 @@ def produce_clan_modules_docs() -> None:
|
||||
f"Environment variables are not set correctly: $CLAN_MODULES_READMES={CLAN_MODULES_READMES}"
|
||||
)
|
||||
|
||||
if not CLAN_MODULES_META:
|
||||
raise ValueError(
|
||||
f"Environment variables are not set correctly: $CLAN_MODULES_META={CLAN_MODULES_META}"
|
||||
)
|
||||
|
||||
if not OUT:
|
||||
raise ValueError(f"Environment variables are not set correctly: $out={OUT}")
|
||||
|
||||
@ -207,6 +245,10 @@ def produce_clan_modules_docs() -> None:
|
||||
with open(CLAN_MODULES_READMES) as readme:
|
||||
readme_map: dict[str, str] = json.load(readme)
|
||||
|
||||
with open(CLAN_MODULES_META) as f:
|
||||
meta_map: dict[str, Any] = json.load(f)
|
||||
print(meta_map)
|
||||
|
||||
# {'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:
|
||||
@ -217,6 +259,11 @@ def produce_clan_modules_docs() -> None:
|
||||
if readme_map.get(module_name, None):
|
||||
output += f"{readme_map[module_name]}\n"
|
||||
|
||||
# Add meta information:
|
||||
# - Inventory implementation status
|
||||
if meta_map.get(module_name, None):
|
||||
output += render_meta(meta_map.get(module_name, {}), module_name)
|
||||
|
||||
output += module_usage(module_name)
|
||||
|
||||
output += options_head if len(options.items()) else ""
|
||||
|
@ -53,8 +53,6 @@
|
||||
./nixosModules/flake-module.nix
|
||||
./pkgs/flake-module.nix
|
||||
./templates/flake-module.nix
|
||||
|
||||
./inventory/flake-module.nix
|
||||
];
|
||||
}
|
||||
);
|
||||
|
@ -13,7 +13,6 @@ let
|
||||
inherit lib clan-core;
|
||||
inherit (inputs) nixpkgs;
|
||||
};
|
||||
|
||||
cfg = config.clan;
|
||||
in
|
||||
{
|
||||
@ -91,6 +90,9 @@ in
|
||||
clanInternals = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
inventory = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
||||
inventoryFile = lib.mkOption { type = lib.types.unspecified; };
|
||||
|
||||
meta = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
||||
all-machines-json = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
||||
machines = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
||||
|
@ -1,57 +0,0 @@
|
||||
# Inventory
|
||||
|
||||
This part provides a specification for the inventory.
|
||||
|
||||
It is used for design phase and as validation helper.
|
||||
|
||||
> Cue is less verbose and easier to understand and maintain than json-schema.
|
||||
> Json-schema, if needed can be easily generated on-the fly.
|
||||
|
||||
## Checking validity
|
||||
|
||||
Directly check a json against the schema
|
||||
|
||||
`cue vet inventory.json root.cue -d '#Root'`
|
||||
|
||||
## Json schema
|
||||
|
||||
Export the json-schema i.e. for usage in python / javascript / nix
|
||||
|
||||
`cue export --out openapi root.cue`
|
||||
|
||||
## Usage
|
||||
|
||||
Comments are rendered as descriptions in the json schema.
|
||||
|
||||
```cue
|
||||
// A name of the clan (primarily shown by the UI)
|
||||
name: string
|
||||
```
|
||||
|
||||
Cue open sets. In the following `foo = {...}` means that the key `foo` can contain any arbitrary json object.
|
||||
|
||||
```cue
|
||||
foo: { ... }
|
||||
```
|
||||
|
||||
Cue dynamic keys.
|
||||
|
||||
```cue
|
||||
[string]: {
|
||||
attr: string
|
||||
}
|
||||
```
|
||||
|
||||
This is the schema of
|
||||
|
||||
```json
|
||||
{
|
||||
"a": {
|
||||
"attr": "foo"
|
||||
},
|
||||
"b": {
|
||||
"attr": "bar"
|
||||
}
|
||||
// ... Indefinitely more dynamic keys of type "string"
|
||||
}
|
||||
```
|
@ -1,137 +0,0 @@
|
||||
{
|
||||
description = "<Put your description here>";
|
||||
|
||||
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||
|
||||
outputs =
|
||||
{ clan-core, ... }:
|
||||
let
|
||||
pkgs = clan-core.inputs.nixpkgs.legacyPackages.${system};
|
||||
system = "x86_64-linux";
|
||||
in
|
||||
# Usage see: https://docs.clan.lol
|
||||
# nice_flake_interface -> buildInventory() -> Inventory -> buildClanFromInventory() -> nixosConfigurations
|
||||
# buildClanFromInventory = inventory: evalModules {
|
||||
# extraAttrs = { inherit inventory; };
|
||||
# # (attrNames inventory.machines)
|
||||
# };
|
||||
# clan =
|
||||
# clan-core.lib.buildClanFromInventory [
|
||||
# # Inventory 0 (loads the json file managed by the Python API)
|
||||
# (builtins.fromJSON (builtins.readFile ./inventory.json))
|
||||
# # ->
|
||||
# # {
|
||||
# # services."backups_1".autoIncludeMachines = true;
|
||||
# # services."backups_1".module = "borgbackup";
|
||||
# # ... etc.
|
||||
# # }
|
||||
# ]
|
||||
# ++ (buildInventory {
|
||||
# clanName = "nice_flake_interface";
|
||||
# description = "A nice flake interface";
|
||||
# icon = "assets/icon.png";
|
||||
# machines = {
|
||||
# jon = {
|
||||
# # Just regular nixos/clan configuration ?
|
||||
# # config = {
|
||||
# # imports = [
|
||||
# # ./modules/shared.nix
|
||||
# # ./machines/jon/configuration.nix
|
||||
# # ];
|
||||
# # nixpkgs.hostPlatform = system;
|
||||
# # # Set this for clan commands use ssh i.e. `clan machines update`
|
||||
# # # If you change the hostname, you need to update this line to root@<new-hostname>
|
||||
# # # This only works however if you have avahi running on your admin machine else use IP
|
||||
# # clan.networking.targetHost = pkgs.lib.mkDefault "root@jon";
|
||||
# # # ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT
|
||||
# # disko.devices.disk.main = {
|
||||
# # device = "/dev/disk/by-id/__CHANGE_ME__";
|
||||
# # };
|
||||
# # # IMPORTANT! Add your SSH key here
|
||||
# # # e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
# # users.users.root.openssh.authorizedKeys.keys = throw ''
|
||||
# # Don't forget to add your SSH key here!
|
||||
# # users.users.root.openssh.authorizedKeys.keys = [ "<YOUR SSH_KEY>" ]
|
||||
# # '';
|
||||
# # # Zerotier needs one controller to accept new nodes. Once accepted
|
||||
# # # the controller can be offline and routing still works.
|
||||
# # clan.networking.zerotier.controller.enable = true;
|
||||
# # };
|
||||
# };
|
||||
# };
|
||||
# })
|
||||
# ++ [
|
||||
# # Low level inventory overrides (comes at the end)
|
||||
# {
|
||||
# services."backups_2".autoIncludeMachines = true;
|
||||
# services."backups_2".module = "borgbackup";
|
||||
# }
|
||||
# ];
|
||||
# # buildClan :: [ Partial<Inventory> ] -> Inventory
|
||||
# # foldl' (acc: v: lib.recursiveUpdate acc v) {} []
|
||||
# inventory = [
|
||||
# # import json
|
||||
# {...}
|
||||
# # power user flake
|
||||
# {...}
|
||||
# ]
|
||||
# # With Module system
|
||||
# # Pros: Easy to understand,
|
||||
# # Cons: Verbose, hard to maintain
|
||||
# # buildClan :: { modules = [ { config = Partial<Inventory>; options :: InventoryOptions; } } ]; } -> Inventory
|
||||
# eval = lib.evalModules {
|
||||
# modules = [
|
||||
# {
|
||||
# # Inventory Schema
|
||||
# # Python validation
|
||||
# options = {...}
|
||||
# }
|
||||
# {
|
||||
# config = map lib.mkDefault
|
||||
# (builtins.fromJSON (builtins.readFile ./inventory.json))
|
||||
# }
|
||||
# {
|
||||
# # User provided
|
||||
# config = {...}
|
||||
# }
|
||||
# # Later overrides.
|
||||
# {
|
||||
# lib.mkForce ...
|
||||
# }
|
||||
# ];
|
||||
# }
|
||||
# nixosConfigurations = lib.evalModules inventory;
|
||||
# eval.config.inventory
|
||||
# #
|
||||
# eval.config.machines.jon#nixosConfig
|
||||
# eval.config.machines.sara#nixosConfig
|
||||
#
|
||||
# {inventory, config, ...}:{
|
||||
# hostname = config.machines.sara # Invalid
|
||||
# hostname = inventory.machines.sara.hostname # Valid
|
||||
# }
|
||||
/*
|
||||
# Type
|
||||
|
||||
buildInventory :: {
|
||||
clanName :: string
|
||||
machines :: {
|
||||
${name} :: {
|
||||
config :: {
|
||||
# NixOS configuration
|
||||
};
|
||||
};
|
||||
};
|
||||
# ... More mapped inventory options
|
||||
# i.e. shared config for all machines
|
||||
} -> Inventory
|
||||
*/
|
||||
{
|
||||
# all machines managed by Clan
|
||||
inherit (clan) nixosConfigurations clanInternals;
|
||||
# add the Clan cli tool to the dev shell
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
packages = [ clan-core.packages.${system}.clan-cli ];
|
||||
};
|
||||
};
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package machines
|
||||
|
||||
|
||||
#machine: machines: [string]: {
|
||||
name: string,
|
||||
description?: string,
|
||||
icon?: string
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
{
|
||||
"machines": {
|
||||
"camina_machine": {
|
||||
"name": "camina"
|
||||
},
|
||||
"vyr_machine": {
|
||||
"name": "vyr"
|
||||
},
|
||||
"vi_machine": {
|
||||
"name": "vi"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "kenjis clan"
|
||||
},
|
||||
"services": {
|
||||
"backup": {
|
||||
"meta": {
|
||||
"name": "My backup"
|
||||
},
|
||||
"module": "borbackup-static",
|
||||
"machineConfig": {
|
||||
"vyr": {
|
||||
"roles": ["server"]
|
||||
},
|
||||
"vi": {
|
||||
"roles": ["client"]
|
||||
},
|
||||
"camina_machine": {
|
||||
"roles": ["client"]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"folders": ["/home", "/root", "/var", "/etc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
{
|
||||
"machines": {
|
||||
"camina_machine": {
|
||||
"name": "camina"
|
||||
},
|
||||
"vyr": {
|
||||
"name": "vyr"
|
||||
},
|
||||
"vi": {
|
||||
"name": "vi"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "kenjis clan"
|
||||
},
|
||||
"services": {
|
||||
"sync_files": {
|
||||
"meta": {
|
||||
"name": "My sync"
|
||||
},
|
||||
"module": "syncthing-static-peers",
|
||||
"machineConfig": {
|
||||
"vyr": {},
|
||||
"vi": {},
|
||||
"camina_machine": {}
|
||||
},
|
||||
"config": {
|
||||
"folders": {
|
||||
"test": {
|
||||
"path": "~/data/docs",
|
||||
"devices": ["camina", "vyr", "vi"]
|
||||
},
|
||||
"videos": {
|
||||
"path": "~/data/videos",
|
||||
"devices": ["camina", "vyr", "ezra"]
|
||||
},
|
||||
"playlist": {
|
||||
"path": "~/data/playlist",
|
||||
"devices": ["camina", "vyr", "ezra"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
{
|
||||
"machines": {
|
||||
"camina_machine": {
|
||||
"name": "camina"
|
||||
},
|
||||
"vyr_machine": {
|
||||
"name": "vyr"
|
||||
},
|
||||
"vi_machine": {
|
||||
"name": "vi"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "kenjis clan"
|
||||
},
|
||||
"services": {
|
||||
"backup": {
|
||||
"meta": {
|
||||
"name": "My backup"
|
||||
},
|
||||
"module": "borbackup-static",
|
||||
"machineConfig": {
|
||||
"vyr_machine": {
|
||||
"roles": ["server"]
|
||||
},
|
||||
"vi_machine": {
|
||||
"roles": ["peer"]
|
||||
},
|
||||
"camina_machine": {
|
||||
"roles": ["peer"]
|
||||
}
|
||||
},
|
||||
"config": {}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,53 +12,83 @@
|
||||
# DEPRECATED: use meta.icon instead
|
||||
clanIcon ? null, # A path to an icon to be used for the clan, should be the same for all machines
|
||||
meta ? { }, # A set containing clan meta: name :: string, icon :: string, description :: string
|
||||
pkgsForSystem ? (_system: null), # A map from arch to pkgs, if specified this nixpkgs will be only imported once for each system.
|
||||
# A map from arch to pkgs, if specified this nixpkgs will be only imported once for each system.
|
||||
# This improves performance, but all nipxkgs.* options will be ignored.
|
||||
pkgsForSystem ? (_system: null),
|
||||
/*
|
||||
Low level inventory configuration.
|
||||
Overrides the services configuration.
|
||||
*/
|
||||
inventory ? { },
|
||||
}:
|
||||
let
|
||||
deprecationWarnings = [
|
||||
(lib.warnIf (
|
||||
clanName != null
|
||||
) "clanName is deprecated, please use meta.name instead. ${clanName}" null)
|
||||
(lib.warnIf (clanIcon != null) "clanIcon is deprecated, please use meta.icon instead" null)
|
||||
# Internal inventory, this is the result of merging all potential inventory sources:
|
||||
# - Default instances configured via 'services'
|
||||
# - The inventory overrides
|
||||
# - Machines that exist in inventory.machines
|
||||
# - Machines explicitly configured via 'machines' argument
|
||||
# - Machines that exist in the machines directory
|
||||
# Checks on the module level:
|
||||
# - Each service role must reference a valid machine after all machines are merged
|
||||
mergedInventory =
|
||||
(lib.evalModules {
|
||||
modules = [
|
||||
clan-core.lib.inventory.interface
|
||||
{ inherit meta; }
|
||||
(
|
||||
if
|
||||
builtins.pathExists "${directory}/inventory.json"
|
||||
# Is recursively applied. Any explicit nix will override.
|
||||
then
|
||||
lib.mkDefault (builtins.fromJSON (builtins.readFile "${directory}/inventory.json"))
|
||||
else
|
||||
{ }
|
||||
)
|
||||
inventory
|
||||
# Machines explicitly configured via 'machines' argument
|
||||
{
|
||||
# { ${name} :: meta // { name, tags } }
|
||||
machines = lib.mapAttrs (
|
||||
name: config:
|
||||
(lib.attrByPath [
|
||||
"clan"
|
||||
"meta"
|
||||
] { } config)
|
||||
// {
|
||||
# meta.name default is the attribute name of the machine
|
||||
name = lib.mkDefault (
|
||||
lib.attrByPath [
|
||||
"clan"
|
||||
"meta"
|
||||
"name"
|
||||
] name config
|
||||
);
|
||||
tags = lib.attrByPath [
|
||||
"clan"
|
||||
"tags"
|
||||
] [ ] config;
|
||||
}
|
||||
) machines;
|
||||
}
|
||||
# Will be deprecated
|
||||
{ machines = lib.mapAttrs (_n: _: lib.mkDefault { }) machinesDirs; }
|
||||
|
||||
# Deprecated interface
|
||||
(if clanName != null then { meta.name = clanName; } else { })
|
||||
(if clanIcon != null then { meta.icon = clanIcon; } else { })
|
||||
];
|
||||
}).config;
|
||||
|
||||
inherit (clan-core.lib.inventory) buildInventory;
|
||||
|
||||
# map from machine name to service configuration
|
||||
# { ${machineName} :: Config }
|
||||
serviceConfigs = buildInventory mergedInventory;
|
||||
|
||||
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (
|
||||
builtins.readDir (directory + /machines)
|
||||
);
|
||||
|
||||
mergedMeta =
|
||||
let
|
||||
metaFromFile =
|
||||
if (builtins.pathExists "${directory}/clan/meta.json") then
|
||||
let
|
||||
settings = builtins.fromJSON (builtins.readFile "${directory}/clan/meta.json");
|
||||
in
|
||||
settings
|
||||
else
|
||||
{ };
|
||||
legacyMeta = lib.filterAttrs (_: v: v != null) {
|
||||
name = clanName;
|
||||
icon = clanIcon;
|
||||
};
|
||||
optionsMeta = lib.filterAttrs (_: v: v != null) meta;
|
||||
|
||||
warnings =
|
||||
builtins.map (
|
||||
name:
|
||||
if
|
||||
metaFromFile.${name} or null != optionsMeta.${name} or null && optionsMeta.${name} or null != null
|
||||
then
|
||||
lib.warn "meta.${name} is set in different places. (exlicit option meta.${name} overrides ${directory}/clan/meta.json)" null
|
||||
else
|
||||
null
|
||||
) (builtins.attrNames metaFromFile)
|
||||
++ [ (if (res.name or null == null) then (throw "meta.name should be set") else null) ];
|
||||
res = metaFromFile // legacyMeta // optionsMeta;
|
||||
in
|
||||
# Print out warnings before returning the merged result
|
||||
builtins.deepSeq warnings res;
|
||||
|
||||
machineSettings =
|
||||
machineName:
|
||||
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
|
||||
@ -71,14 +101,16 @@ let
|
||||
builtins.fromJSON (builtins.readFile (directory + /machines/${machineName}/settings.json))
|
||||
);
|
||||
|
||||
# Read additional imports specified via a config option in settings.json
|
||||
# This is not an infinite recursion, because the imports are discovered here
|
||||
# before calling evalModules.
|
||||
# It is still useful to have the imports as an option, as this allows for type
|
||||
# checking and easy integration with the config frontend(s)
|
||||
machineImports =
|
||||
machineSettings: map (module: clan-core.clanModules.${module}) (machineSettings.clanImports or [ ]);
|
||||
|
||||
deprecationWarnings = [
|
||||
(lib.warnIf (
|
||||
clanName != null
|
||||
) "clanName is deprecated, please use meta.name instead. ${clanName}" null)
|
||||
(lib.warnIf (clanIcon != null) "clanIcon is deprecated, please use meta.icon instead" null)
|
||||
];
|
||||
|
||||
# TODO: remove default system once we have a hardware-config mechanism
|
||||
nixosConfiguration =
|
||||
{
|
||||
@ -98,6 +130,9 @@ let
|
||||
clan-core.nixosModules.clanCore
|
||||
extraConfig
|
||||
(machines.${name} or { })
|
||||
# Inherit the inventory assertions ?
|
||||
{ inherit (mergedInventory) assertions; }
|
||||
{ imports = serviceConfigs.${name} or { }; }
|
||||
(
|
||||
{
|
||||
# Settings
|
||||
@ -125,7 +160,7 @@ let
|
||||
} // specialArgs;
|
||||
};
|
||||
|
||||
allMachines = machinesDirs // machines;
|
||||
allMachines = mergedInventory.machines or { };
|
||||
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
@ -177,9 +212,10 @@ builtins.deepSeq deprecationWarnings {
|
||||
inherit nixosConfigurations;
|
||||
|
||||
clanInternals = {
|
||||
# Evaluated clan meta
|
||||
# Merged /clan/meta.json with overrides from buildClan
|
||||
meta = mergedMeta;
|
||||
meta = mergedInventory.meta;
|
||||
inventory = mergedInventory;
|
||||
|
||||
inventoryFile = "${directory}/inventory.json";
|
||||
|
||||
# machine specifics
|
||||
machines = configsPerSystem;
|
||||
|
@ -6,6 +6,7 @@
|
||||
}:
|
||||
{
|
||||
evalClanModules = import ./eval-clan-modules { inherit clan-core nixpkgs lib; };
|
||||
inventory = import ./inventory { inherit lib clan-core; };
|
||||
jsonschema = import ./jsonschema { inherit lib; };
|
||||
modules = import ./description.nix { inherit clan-core lib; };
|
||||
buildClan = import ./build-clan { inherit clan-core lib nixpkgs; };
|
||||
|
@ -5,7 +5,10 @@
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [ ./jsonschema/flake-module.nix ];
|
||||
imports = [
|
||||
./jsonschema/flake-module.nix
|
||||
./inventory/flake-module.nix
|
||||
];
|
||||
flake.lib = import ./default.nix {
|
||||
inherit lib inputs;
|
||||
inherit (inputs) nixpkgs;
|
||||
|
90
lib/inventory/README.md
Normal file
90
lib/inventory/README.md
Normal file
@ -0,0 +1,90 @@
|
||||
# Inventory
|
||||
|
||||
The inventory is our concept for distributed services. Users can configure multiple machines with minimal effort.
|
||||
|
||||
- The inventory acts as a declarative source of truth for all machine configurations.
|
||||
- Users can easily add or remove machines to/from services.
|
||||
- Ensures that all machines and services are configured consistently, across multiple nixosConfigs.
|
||||
- Defaults and predefined roles in our modules minimizes the need for manual configuration.
|
||||
|
||||
Open questions:
|
||||
|
||||
- [ ] How do we set default role, description and other metadata?
|
||||
- It must be accessible from Python.
|
||||
- It must set the value in the module system.
|
||||
|
||||
- [ ] Inventory might use assertions. Should each machine inherit the inventory assertions ?
|
||||
|
||||
- [ ] Is the service config interface the same as the module config interface ?
|
||||
|
||||
- [ ] As a user do I want to see borgbackup as the high level category?
|
||||
|
||||
|
||||
Architecture
|
||||
|
||||
```
|
||||
nixosConfig < machine_module < inventory
|
||||
---------------------------------------------
|
||||
nixos < borgbackup <- inventory <-> UI
|
||||
|
||||
creates the config Maps from high level services to the borgbackup clan module
|
||||
for ONE machine Inventory is completely serializable.
|
||||
UI can interact with the inventory to define machines, and services
|
||||
Defining Users is out of scope for the first prototype.
|
||||
```
|
||||
|
||||
## Provides a specification for the inventory
|
||||
|
||||
It is used for design phase and as validation helper.
|
||||
|
||||
> Cue is less verbose and easier to understand and maintain than json-schema.
|
||||
> Json-schema, if needed can be easily generated on-the fly.
|
||||
|
||||
## Checking validity
|
||||
|
||||
Directly check a json against the schema
|
||||
|
||||
`cue vet inventory.json root.cue -d '#Root'`
|
||||
|
||||
## Json schema
|
||||
|
||||
Export the json-schema i.e. for usage in python / javascript / nix
|
||||
|
||||
`cue export --out openapi root.cue`
|
||||
|
||||
## Usage
|
||||
|
||||
Comments are rendered as descriptions in the json schema.
|
||||
|
||||
```cue
|
||||
// A name of the clan (primarily shown by the UI)
|
||||
name: string
|
||||
```
|
||||
|
||||
Cue open sets. In the following `foo = {...}` means that the key `foo` can contain any arbitrary json object.
|
||||
|
||||
```cue
|
||||
foo: { ... }
|
||||
```
|
||||
|
||||
Cue dynamic keys.
|
||||
|
||||
```cue
|
||||
[string]: {
|
||||
attr: string
|
||||
}
|
||||
```
|
||||
|
||||
This is the schema of
|
||||
|
||||
```json
|
||||
{
|
||||
"a": {
|
||||
"attr": "foo"
|
||||
},
|
||||
"b": {
|
||||
"attr": "bar"
|
||||
}
|
||||
// ... Indefinitely more dynamic keys of type "string"
|
||||
}
|
||||
```
|
106
lib/inventory/build-inventory/default.nix
Normal file
106
lib/inventory/build-inventory/default.nix
Normal file
@ -0,0 +1,106 @@
|
||||
# Generate partial NixOS configurations for every machine in the inventory
|
||||
# This function is responsible for generating the module configuration for every machine in the inventory.
|
||||
{ lib, clan-core }:
|
||||
inventory:
|
||||
let
|
||||
machines = machinesFromInventory inventory;
|
||||
|
||||
resolveTags =
|
||||
# Inventory, { machines :: [string], tags :: [string] }
|
||||
inventory: members: {
|
||||
machines =
|
||||
members.machines or [ ]
|
||||
++ (builtins.foldl' (
|
||||
acc: tag:
|
||||
let
|
||||
tagMembers = builtins.attrNames (
|
||||
lib.filterAttrs (_n: v: builtins.elem tag v.tags or [ ]) inventory.machines
|
||||
);
|
||||
in
|
||||
# throw "Machine tag ${tag} not found. Not machine with: tag ${tagName} not in inventory.";
|
||||
if tagMembers == [ ] then
|
||||
throw "Machine tag ${tag} not found. Not machine with: tag ${tag} not in inventory."
|
||||
else
|
||||
acc ++ tagMembers
|
||||
) [ ] members.tags or [ ]);
|
||||
};
|
||||
|
||||
/*
|
||||
Returns a NixOS configuration for every machine in the inventory.
|
||||
|
||||
machinesFromInventory :: Inventory -> { ${machine_name} :: NixOSConfiguration }
|
||||
*/
|
||||
machinesFromInventory =
|
||||
inventory:
|
||||
# For every machine in the inventory, build a NixOS configuration
|
||||
# For each machine generate config, forEach service, if the machine is used.
|
||||
builtins.mapAttrs (
|
||||
machineName: _:
|
||||
lib.foldlAttrs (
|
||||
# [ Modules ], String, { ${instance_name} :: ServiceConfig }
|
||||
acc: moduleName: serviceConfigs:
|
||||
acc
|
||||
# Collect service config
|
||||
++ (lib.foldlAttrs (
|
||||
# [ Modules ], String, ServiceConfig
|
||||
acc2: instanceName: serviceConfig:
|
||||
let
|
||||
resolvedRoles = builtins.mapAttrs (
|
||||
_roleName: members: resolveTags inventory members
|
||||
) serviceConfig.roles;
|
||||
|
||||
isInService = builtins.any (members: builtins.elem machineName members.machines) (
|
||||
builtins.attrValues resolvedRoles
|
||||
);
|
||||
|
||||
# Inverse map of roles. Allows for easy lookup of roles for a given machine.
|
||||
# { ${machine_name} :: [roles]
|
||||
inverseRoles = lib.foldlAttrs (
|
||||
acc: roleName:
|
||||
{ machines }:
|
||||
acc
|
||||
// builtins.foldl' (
|
||||
acc2: machineName: acc2 // { ${machineName} = (acc.${machineName} or [ ]) ++ [ roleName ]; }
|
||||
) { } machines
|
||||
) { } resolvedRoles;
|
||||
|
||||
machineServiceConfig = (serviceConfig.machines.${machineName} or { }).config or { };
|
||||
globalConfig = serviceConfig.config or { };
|
||||
|
||||
# TODO: maybe optimize this dont lookup the role in inverse roles. Imports are not lazy
|
||||
roleModules = builtins.map (
|
||||
role:
|
||||
let
|
||||
path = "${clan-core.clanModules.${moduleName}}/roles/${role}.nix";
|
||||
in
|
||||
if builtins.pathExists path then
|
||||
path
|
||||
else
|
||||
throw "Role doesnt have a module: ${role}. Path: ${path} not found."
|
||||
) inverseRoles.${machineName} or [ ];
|
||||
in
|
||||
if isInService then
|
||||
acc2
|
||||
++ [
|
||||
{
|
||||
imports = [ clan-core.clanModules.${moduleName} ] ++ roleModules;
|
||||
config.clan.${moduleName} = lib.mkMerge [
|
||||
globalConfig
|
||||
machineServiceConfig
|
||||
];
|
||||
}
|
||||
{
|
||||
config.clan.inventory.services.${moduleName}.${instanceName} = {
|
||||
roles = resolvedRoles;
|
||||
# TODO: Add inverseRoles to the service config if needed
|
||||
# inherit inverseRoles;
|
||||
};
|
||||
}
|
||||
]
|
||||
else
|
||||
acc2
|
||||
) [ ] serviceConfigs)
|
||||
) [ ] inventory.services
|
||||
) inventory.machines;
|
||||
in
|
||||
machines
|
121
lib/inventory/build-inventory/interface.nix
Normal file
121
lib/inventory/build-inventory/interface.nix
Normal file
@ -0,0 +1,121 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
t = lib.types;
|
||||
|
||||
metaOptions = {
|
||||
name = lib.mkOption { type = t.str; };
|
||||
description = lib.mkOption {
|
||||
default = null;
|
||||
type = t.nullOr t.str;
|
||||
};
|
||||
icon = lib.mkOption {
|
||||
default = null;
|
||||
type = t.nullOr t.str;
|
||||
};
|
||||
};
|
||||
|
||||
machineRef = lib.mkOptionType {
|
||||
name = "machineRef";
|
||||
description = "Machine :: [${builtins.concatStringsSep " | " (builtins.attrNames config.machines)}]";
|
||||
check = v: lib.isString v && builtins.elem v (builtins.attrNames config.machines);
|
||||
merge = lib.mergeEqualOption;
|
||||
};
|
||||
|
||||
allTags = lib.unique (
|
||||
lib.foldlAttrs (
|
||||
tags: _: m:
|
||||
tags ++ m.tags or [ ]
|
||||
) [ ] config.machines
|
||||
);
|
||||
|
||||
tagRef = lib.mkOptionType {
|
||||
name = "tagRef";
|
||||
description = "Tags :: [${builtins.concatStringsSep " | " allTags}]";
|
||||
check = v: lib.isString v && builtins.elem v allTags;
|
||||
merge = lib.mergeEqualOption;
|
||||
};
|
||||
in
|
||||
{
|
||||
options.assertions = lib.mkOption {
|
||||
type = t.listOf t.unspecified;
|
||||
internal = true;
|
||||
default = [ ];
|
||||
};
|
||||
config.assertions = lib.foldlAttrs (
|
||||
ass1: serviceName: c:
|
||||
ass1
|
||||
++ lib.foldlAttrs (
|
||||
ass2: instanceName: instanceConfig:
|
||||
let
|
||||
serviceMachineNames = lib.attrNames instanceConfig.machines;
|
||||
topLevelMachines = lib.attrNames config.machines;
|
||||
# All machines must be defined in the top-level machines
|
||||
assertions = builtins.map (m: {
|
||||
assertion = builtins.elem m topLevelMachines;
|
||||
message = "${serviceName}.${instanceName}.machines.${m}. Should be one of [ ${builtins.concatStringsSep " | " topLevelMachines} ]";
|
||||
}) serviceMachineNames;
|
||||
in
|
||||
ass2 ++ assertions
|
||||
) [ ] c
|
||||
) [ ] config.services;
|
||||
|
||||
options.meta = metaOptions;
|
||||
|
||||
options.machines = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.submodule {
|
||||
options = {
|
||||
inherit (metaOptions) name description icon;
|
||||
tags = lib.mkOption {
|
||||
default = [ ];
|
||||
apply = lib.unique;
|
||||
type = t.listOf t.str;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
options.services = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.attrsOf (
|
||||
t.submodule {
|
||||
options.meta = metaOptions;
|
||||
options.config = lib.mkOption {
|
||||
default = { };
|
||||
type = t.anything;
|
||||
};
|
||||
options.machines = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.submodule {
|
||||
options.config = lib.mkOption {
|
||||
default = { };
|
||||
type = t.anything;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
options.roles = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.submodule {
|
||||
options.machines = lib.mkOption {
|
||||
default = [ ];
|
||||
type = t.listOf machineRef;
|
||||
};
|
||||
options.tags = lib.mkOption {
|
||||
default = [ ];
|
||||
apply = lib.unique;
|
||||
type = t.listOf tagRef;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
5
lib/inventory/default.nix
Normal file
5
lib/inventory/default.nix
Normal file
@ -0,0 +1,5 @@
|
||||
{ lib, clan-core }:
|
||||
{
|
||||
buildInventory = import ./build-inventory { inherit lib clan-core; };
|
||||
interface = ./build-inventory/interface.nix;
|
||||
}
|
38
lib/inventory/example.nix
Normal file
38
lib/inventory/example.nix
Normal file
@ -0,0 +1,38 @@
|
||||
{ self, ... }:
|
||||
self.lib.buildClan {
|
||||
# Name of the clan in the UI, should be unique
|
||||
meta.name = "Inventory clan";
|
||||
|
||||
# Should usually point to the directory of flake.nix
|
||||
directory = self;
|
||||
|
||||
inventory = {
|
||||
services = {
|
||||
borgbackup.instance_1 = {
|
||||
roles.server.machines = [ "backup_server" ];
|
||||
roles.client.tags = [ "backup" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# merged with
|
||||
machines = {
|
||||
"backup_server" = {
|
||||
clan.tags = [ "all" ];
|
||||
};
|
||||
"client_1_machine" = {
|
||||
clan.tags = [
|
||||
"all"
|
||||
"backup"
|
||||
];
|
||||
};
|
||||
"client_2_machine" = {
|
||||
clan.tags = [
|
||||
"all"
|
||||
"backup"
|
||||
];
|
||||
# Name of the machine in the UI
|
||||
clan.meta.name = "camina";
|
||||
};
|
||||
};
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
mkdir -p $out
|
||||
'';
|
||||
};
|
||||
|
||||
devShells.inventory-schema = pkgs.mkShell { inputsFrom = [ config.packages.inventory-schema ]; };
|
||||
|
||||
checks.inventory-schema-checks = pkgs.stdenv.mkDerivation {
|
@ -1,8 +1,7 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"clan.lol/inventory/services"
|
||||
"clan.lol/inventory/machines"
|
||||
"clan.lol/inventory/schema"
|
||||
)
|
||||
|
||||
@jsonschema(schema="http://json-schema.org/schema#")
|
||||
@ -16,9 +15,9 @@ import (
|
||||
icon?: string
|
||||
}
|
||||
|
||||
// A map of services
|
||||
services.#service
|
||||
// // A map of services
|
||||
schema.#service
|
||||
|
||||
// A map of machines
|
||||
machines.#machine
|
||||
// // A map of machines
|
||||
schema.#machine
|
||||
}
|
@ -1,22 +1,29 @@
|
||||
package services
|
||||
package schema
|
||||
|
||||
#ServiceRole: "server" | "client" | "both"
|
||||
#machine: machines: [string]: {
|
||||
name: string,
|
||||
description?: string,
|
||||
icon?: string
|
||||
tags: [...string]
|
||||
}
|
||||
|
||||
#service: services: [string]: {
|
||||
#role: string
|
||||
|
||||
#service: services: [string]: [string]: {
|
||||
// Required meta fields
|
||||
meta: {
|
||||
name: string,
|
||||
icon?: string
|
||||
description?: string,
|
||||
},
|
||||
// Required module specifies the behavior of the service.
|
||||
module: string,
|
||||
|
||||
// We moved the machine sepcific config to "machines".
|
||||
// It may be moved back depending on what makes more sense in the future.
|
||||
machineConfig: {
|
||||
roles: [#role]: {
|
||||
machines: [...string],
|
||||
tags: [...string],
|
||||
}
|
||||
machines: {
|
||||
[string]: {
|
||||
roles?: [ ...#ServiceRole ],
|
||||
config?: {
|
||||
...
|
||||
}
|
53
lib/inventory/src/tests/borgbackup.json
Normal file
53
lib/inventory/src/tests/borgbackup.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"machines": {
|
||||
"camina_machine": {
|
||||
"name": "camina",
|
||||
"tags": ["laptop"]
|
||||
},
|
||||
"vyr_machine": {
|
||||
"name": "vyr"
|
||||
},
|
||||
"vi_machine": {
|
||||
"name": "vi",
|
||||
"tags": ["laptop"]
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "kenjis clan"
|
||||
},
|
||||
"services": {
|
||||
"borgbackup": {
|
||||
"instance_1": {
|
||||
"meta": {
|
||||
"name": "My backup"
|
||||
},
|
||||
"roles": {
|
||||
"server": {
|
||||
"machines": ["vyr_machine"]
|
||||
},
|
||||
"client": {
|
||||
"machines": ["vyr_machine"],
|
||||
"tags": ["laptop"]
|
||||
}
|
||||
},
|
||||
"machines": {},
|
||||
"config": {}
|
||||
},
|
||||
"instance_2": {
|
||||
"meta": {
|
||||
"name": "My backup"
|
||||
},
|
||||
"roles": {
|
||||
"server": {
|
||||
"machines": ["vi_machine"]
|
||||
},
|
||||
"client": {
|
||||
"machines": ["camina_machine"]
|
||||
}
|
||||
},
|
||||
"machines": {},
|
||||
"config": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
lib/inventory/src/tests/syncthing.json
Normal file
47
lib/inventory/src/tests/syncthing.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"machines": {
|
||||
"camina_machine": {
|
||||
"name": "camina"
|
||||
},
|
||||
"vyr_machine": {
|
||||
"name": "vyr"
|
||||
},
|
||||
"vi_machine": {
|
||||
"name": "vi"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "kenjis clan"
|
||||
},
|
||||
"services": {
|
||||
"syncthing": {
|
||||
"instance_1": {
|
||||
"meta": {
|
||||
"name": "My sync"
|
||||
},
|
||||
"roles": {
|
||||
"peer": {
|
||||
"machines": ["vyr_machine", "vi_machine", "camina_machine"]
|
||||
}
|
||||
},
|
||||
"machines": {},
|
||||
"config": {
|
||||
"folders": {
|
||||
"test": {
|
||||
"path": "~/data/docs",
|
||||
"devices": ["camina_machine", "vyr_machine", "vi_machine"]
|
||||
},
|
||||
"videos": {
|
||||
"path": "~/data/videos",
|
||||
"devices": ["camina_machine", "vyr_machine"]
|
||||
},
|
||||
"playlist": {
|
||||
"path": "~/data/playlist",
|
||||
"devices": ["camina_machine", "vi_machine"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
lib/inventory/src/tests/zerotier.json
Normal file
36
lib/inventory/src/tests/zerotier.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"machines": {
|
||||
"camina_machine": {
|
||||
"name": "camina"
|
||||
},
|
||||
"vyr_machine": {
|
||||
"name": "vyr"
|
||||
},
|
||||
"vi_machine": {
|
||||
"name": "vi"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "kenjis clan"
|
||||
},
|
||||
"services": {
|
||||
"zerotier": {
|
||||
"instance_1": {
|
||||
"meta": {
|
||||
"name": "My Network"
|
||||
},
|
||||
"roles": {
|
||||
"controller": { "machines": ["vyr_machine"] },
|
||||
"moon": { "machines": ["vyr_machine"] },
|
||||
"peer": { "machines": ["vi_machine", "camina_machine"] }
|
||||
},
|
||||
"machines": {
|
||||
"vyr_machine": {
|
||||
"config": {}
|
||||
}
|
||||
},
|
||||
"config": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,5 +14,8 @@
|
||||
./vm.nix
|
||||
./wayland-proxy-virtwl.nix
|
||||
./zerotier
|
||||
# Inventory
|
||||
./inventory/interface.nix
|
||||
./meta/interface.nix
|
||||
];
|
||||
}
|
||||
|
35
nixosModules/clanCore/inventory/interface.nix
Normal file
35
nixosModules/clanCore/inventory/interface.nix
Normal file
@ -0,0 +1,35 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
# {
|
||||
# roles = {
|
||||
# client = {
|
||||
# machines = [
|
||||
# "camina_machine"
|
||||
# "vi_machine"
|
||||
# ];
|
||||
# };
|
||||
# server = {
|
||||
# machines = [ "vyr_machine" ];
|
||||
# };
|
||||
# };
|
||||
# }
|
||||
instanceOptions = lib.types.submodule {
|
||||
options.roles = lib.mkOption { type = lib.types.attrsOf machinesList; };
|
||||
};
|
||||
|
||||
# {
|
||||
# machines = [
|
||||
# "camina_machine"
|
||||
# "vi_machine"
|
||||
# "vyr_machine"
|
||||
# ];
|
||||
# }
|
||||
machinesList = lib.types.submodule {
|
||||
options.machines = lib.mkOption { type = lib.types.listOf lib.types.str; };
|
||||
};
|
||||
in
|
||||
{
|
||||
options.clan.inventory.services = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.attrsOf instanceOptions);
|
||||
};
|
||||
}
|
10
nixosModules/clanCore/meta/interface.nix
Normal file
10
nixosModules/clanCore/meta/interface.nix
Normal file
@ -0,0 +1,10 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
optStr = lib.types.nullOr lib.types.str;
|
||||
in
|
||||
{
|
||||
options.clan.meta.name = lib.mkOption { type = lib.types.str; };
|
||||
options.clan.meta.description = lib.mkOption { type = optStr; };
|
||||
options.clan.meta.icon = lib.mkOption { type = optStr; };
|
||||
options.clan.tags = lib.mkOption { type = lib.types.listOf lib.types.str; };
|
||||
}
|
@ -11,7 +11,19 @@
|
||||
# Usage see: https://docs.clan.lol
|
||||
clan = clan-core.lib.buildClan {
|
||||
directory = self;
|
||||
clanName = "__CHANGE_ME__"; # Ensure this is internet wide unique.
|
||||
meta.name = "__CHANGE_ME__"; # Ensure this is internet wide unique.
|
||||
|
||||
# Distributed services, uncomment to enable.
|
||||
# inventory = {
|
||||
# services = {
|
||||
# # This example configures a BorgBackup service
|
||||
# # Check: https://docs.clan.lol/reference/clanModules which ones are available in Inventory
|
||||
# borgbackup.instance_1 = {
|
||||
# roles.server.machines = [ "jon" ];
|
||||
# roles.client.machines = [ "sara" ];
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
|
||||
# Prerequisite: boot into the installer
|
||||
# See: https://docs.clan.lol/getting-started/installer
|
||||
|
Loading…
Reference in New Issue
Block a user