1
0
forked from clan/clan-core

Compare commits

...

310 Commits

Author SHA1 Message Date
7aab366197 zerotier-static: halalify all packages 2024-07-02 13:05:22 +02:00
b7d66f9814 wip 2024-07-02 12:51:11 +02:00
6b70792cae Merge pull request 'remove unused multi machine clan urls' (#1687) from Mic92-main into main 2024-07-02 10:26:14 +00:00
5467f0256a Merge pull request 'zerotier-peers: use halalified package' (#1686) from kenji/clan-core:zerotier-static/halalify into main
Reviewed-on: clan/clan-core#1686
2024-07-02 10:24:07 +00:00
88cd52fd0f zerotier-peers: use halalified package 2024-07-02 10:24:07 +00:00
e637394370 remove unused multi machine clan urls 2024-07-02 12:20:30 +02:00
c4bbdb2212 Merge pull request 'API: add performance constraints' (#1685) from hsjobeki/clan-core:hsjobeki-main into main 2024-07-02 10:18:54 +00:00
d93deacb4b
API: remove performance constraints 2024-07-02 12:15:37 +02:00
16c9aa99a9
API: add performance constraints 2024-07-02 12:07:45 +02:00
1448e593e9 Merge pull request 'machines/machines: drop unused qmp wrapper' (#1683) from Mic92-main into main 2024-07-02 10:01:43 +00:00
815bb336be remove unused MachineParams 2024-07-02 11:57:39 +02:00
4bdcc4dd5e Merge pull request 'api: clan Modules add readme content' (#1684) from hsjobeki/clan-core:hsjobeki-main into main 2024-07-02 09:49:23 +00:00
5ab22d043d
api: clan Modules add readme content 2024-07-02 11:45:48 +02:00
47010f458c machines/machines: drop unused qmp wrapper 2024-07-02 11:42:02 +02:00
58b9e5e66e Merge pull request 'vars: add generators.<name>.finalScript' (#1682) from DavHau/clan-core:DavHau-dave into main 2024-07-02 09:41:22 +00:00
a7d1ea455b vars: add generators.<name>.finalScript 2024-07-02 16:37:31 +07:00
f37d0c746d Merge pull request 'API: migrate add machine to inventory' (#1676) from hsjobeki/clan-core:hsjobeki-main into main 2024-07-02 09:25:01 +00:00
1b7369cf0d
Fix test 2024-07-02 11:21:52 +02:00
f7c80834cb
Inventory persistence improves error resistance 2024-07-02 11:16:54 +02:00
9f484c1d39
API: migrate machines delete and list to inventory 2024-07-02 11:07:11 +02:00
b73b8fef77 Merge pull request 'clan: implement OSC8 hyperlinks for help output' (#1680) from kenji/clan-core:clan/hyperlink into main
Reviewed-on: clan/clan-core#1680
2024-07-02 08:55:01 +00:00
d9ba61c30a clan: implement OSC8 hyperlinks for help output
The name of the terminal help output stays the same to keep
compatibility with legacy terminal implementations.
2024-07-02 08:55:01 +00:00
33ea53ee8f Merge pull request 'drop duplicate empty template' (#1678) from Mic92-main into main 2024-07-02 08:47:59 +00:00
7c3e7dab60 Merge pull request 'secrets: add settings, generator submodules, improve tests' (#1679) from DavHau/clan-core:DavHau-dave into main 2024-07-02 08:42:12 +00:00
d27e474b66 drop duplicate empty template 2024-07-02 10:41:55 +02:00
d3f31acc5c secrets: add settings, generator submodules, improve tests 2024-07-02 15:38:46 +07:00
1172acdc04 Merge pull request 'Davhau Dave' (#1677) from DavHau/clan-core:DavHau-dave into main 2024-07-02 06:58:28 +00:00
3a0f591c8c vars: simplify eval tests by using submodule directly 2024-07-02 13:55:00 +07:00
df934334a2
API: migrate add machine to inventory 2024-07-01 21:55:42 +02:00
d8380ebb98 Merge pull request 'Inventory: add system and sample machine' (#1675) from hsjobeki/clan-core:hsjobeki-main into main 2024-07-01 19:42:14 +00:00
41f46848b9
Inventory: add missing settings to build the test machine 2024-07-01 21:38:50 +02:00
c678608105
Inventory: add system and sample machine 2024-07-01 21:25:02 +02:00
e7ba8dbe15 Merge pull request 'sshd: workaround for CVE-2024-6387' (#1674) from openssh-cve-workaround into main 2024-07-01 12:04:54 +00:00
cfc09ca270 sshd: workaround for CVE-2024-6387 2024-07-01 14:01:40 +02:00
0f95bfd279 Merge pull request 'fix secret generation on macos' (#1669) from fix-macos-deploy into main 2024-06-30 06:25:39 +00:00
b5a04debf5 Merge pull request 'Various cleanups' (#1670) from Mic92-main into main 2024-06-30 06:24:51 +00:00
498f2c02be formatter/vale: exclude lib
Fixes ci
2024-06-30 08:19:55 +02:00
92669a0d59 nixos-generators: drop nixlib copy 2024-06-30 08:13:24 +02:00
0ead3b477f buildClan: improve deprecation error message of clanName
Printing the clanName in my case was actually more confusing then
helpful. Also mention what function needs to be updated.
2024-06-30 08:08:22 +02:00
05380828c6 Merge pull request 'add Vale for prose linting' (#1370) from fricklerhandwerk/clan-core:add-vale into main
Reviewed-on: clan/clan-core#1370
2024-06-29 09:08:49 +00:00
fca586ff21 fix spelling in README 2024-06-29 09:08:49 +00:00
d40563ea9f add Vale for prose linting 2024-06-29 09:08:49 +00:00
2e2358d850 introduce minifakeroot that also works on macos 2024-06-27 18:53:10 +02:00
bae0a888c9 make bubblewrap in fact generation optional 2024-06-27 18:23:05 +02:00
8f0e537d34 Merge pull request 'vars: init' (#1668) from DavHau/clan-core:DavHau-dave into main 2024-06-27 14:13:38 +00:00
5668bc561d vars: init
- init eval tests
- init basic interface
2024-06-27 21:09:55 +07:00
d4f2f7944c Merge pull request 'update nixpkgs for new nix-unit' (#1667) from DavHau/clan-core:DavHau-dave into main 2024-06-27 12:13:34 +00:00
60076ef492 update nixpkgs for new nix-unit 2024-06-27 19:09:58 +07:00
bc0e727bd7 Merge pull request 'api: add endpoint machine_schema' (#1666) from DavHau/clan-core:DavHau-dave into main 2024-06-27 10:02:11 +00:00
ea87166e44 api: add endpoint machine_schema 2024-06-27 16:58:37 +07:00
27b0d18f0d Merge pull request 'Add toml frontmatter description to jsonschema' (#1664) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-26 15:22:48 +00:00
1628fdeaee
Inventory: add eval tests 2024-06-26 17:19:19 +02:00
2535fdcb12
Inventory: restructure folders 2024-06-26 15:19:40 +02:00
3777a4cf02
Add toml frontmatter description to jsonschema 2024-06-26 15:10:36 +02:00
cecd6011d6 Merge pull request 'flake.lock: update nixpkgs' (#1663) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-26 09:49:40 +00:00
3e001a2809
flake.lock: update nixpkgs 2024-06-26 11:45:40 +02:00
1a8abaa2ac Merge pull request 'clan: state add information about the backup command hooks to the help' (#1661) from kenji/clan-core:clan/improve-description into main
Reviewed-on: clan/clan-core#1661
2024-06-26 08:46:02 +00:00
fa37d528b3 clan: state add information about the backup command hooks to the help 2024-06-26 08:46:02 +00:00
09f7cd7e12 Merge pull request 'ClanModules: Add docs and api to retrieve metadata' (#1662) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-25 19:43:22 +00:00
66d67b18d7
remove module description from json schema 2024-06-25 21:40:04 +02:00
fe21d2edb9
API: add clanModules list and details test 2024-06-25 21:31:44 +02:00
74dd48320e
ClanModules: Add docs and api to retrieve metadata 2024-06-25 21:17:42 +02:00
9b0e2a87e8
ClanModules: Add docs and api to retrieve metadata 2024-06-25 21:17:01 +02:00
4022c13b31 Merge pull request 'Docs: rename clanName to just name' (#1660) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-25 12:25:27 +00:00
25db02368a Merge pull request 'Inventory: init first implementation' (#1638) from hsjobeki/clan-core:wip/inventory into main
Reviewed-on: clan/clan-core#1638
2024-06-25 12:23:29 +00:00
db951f1d9e buildClan: bugfixing 2024-06-25 12:23:29 +00:00
d03422d004 test: increase timeout 2024-06-25 12:23:29 +00:00
4fb15d8733 Add machinesDirs with default 2024-06-25 12:23:29 +00:00
c0293b889c Add machinesDirs with default 2024-06-25 12:23:29 +00:00
26c655ff3c Add settings.json back 2024-06-25 12:23:29 +00:00
712ed3f738 Docs: add inventory module docs 2024-06-25 12:23:29 +00:00
e6c78054c4 Template: make inventory disabled by default 2024-06-25 12:23:29 +00:00
7f674e6f63 Remove useless settings.json 2024-06-25 12:23:29 +00:00
3aa7a6ee69 Inventory: move to lib.inventory 2024-06-25 12:23:29 +00:00
6378a96b4d Template: use inventory 2024-06-25 12:23:29 +00:00
b74590f381 Inventory: simplify build-clan interface 2024-06-25 12:23:29 +00:00
2f8b782a1f Inventory: init module merge & validation logic for inventory 2024-06-25 12:23:29 +00:00
c89080deb4 Extend build-clan interface 2024-06-25 12:23:29 +00:00
e44b07df66 inventory: rename clan.services 2024-06-25 12:23:29 +00:00
afca7ae0cc update inventory implementation 2024-06-25 12:23:29 +00:00
3a9c56deb2 readme improvements 2024-06-25 12:23:29 +00:00
5f72778ade schema improvements 2024-06-25 12:23:29 +00:00
d934b67c72 Inventory: implement borgbackup 2024-06-25 12:23:29 +00:00
241cca5b70
Docs: rename clanName to just name 2024-06-25 14:21:53 +02:00
39ec23bd31 Merge pull request 'clan-cli: Better env jailing in temporary_home' (#1659) from Qubasa/clan-core:Qubasa-main into main 2024-06-25 10:06:55 +00:00
62839b6fa0 clan-cli: Better env jailing in temporary_home 2024-06-25 12:03:44 +02:00
5ae8ccbbdd Merge pull request 'clan: state list improve formatting' (#1658) from kenji/clan-core:clan/state-list-improve into main
Reviewed-on: clan/clan-core#1658
2024-06-25 09:56:29 +00:00
af2ffb7e5e clan: state list improve formatting 2024-06-25 11:50:19 +02:00
d1f2679c45 Merge pull request 'cli: add command to list state' (#1657) from kenji/clan-core:cli/state-list into main
Reviewed-on: clan/clan-core#1657
2024-06-25 09:34:31 +00:00
3bcaeda737 cli: add command to list state
Add a subcommand to list configured state for a specific machine.

Example:
```
$ clan state list [MACHINE]
```
2024-06-25 09:34:31 +00:00
4983c6d302 Merge pull request 'blog: fix typos in backup blog' (#1656) from kenji/clan-core:blog-backups-fix-typo into main
Reviewed-on: clan/clan-core#1656
2024-06-25 09:02:52 +00:00
63e6aaf1fe blog: fix typos in backup blog 2024-06-25 09:02:52 +00:00
b2332e796e Merge pull request 'clan: ssh fix typos' (#1655) from kenji/clan-core:fix-typo into main
Reviewed-on: clan/clan-core#1655
2024-06-25 08:50:36 +00:00
cd8ec83881 clan: ssh fix typos 2024-06-25 10:32:05 +02:00
7ef86e99dc Merge pull request 'clan-cli: Acutally test SecretStore for age and password-store.' (#1654) from Qubasa/clan-core:Qubasa-main into main 2024-06-24 19:45:06 +00:00
70ca824e88 clan-cli: Acutally test SecretStore for age and password-store. 2024-06-24 21:41:16 +02:00
690a1fe64c Merge pull request 'clan-cli: Fix user-password without user-prompt. Remove newlines from user-password and root-password. Improve test_generate_secret' (#1653) from Qubasa/clan-core:Qubasa-main into main 2024-06-24 18:11:26 +00:00
38c0233496 clan-cli: Fix user-password without user-prompt. Remove newlines from user-password and root-password. Improve test_generate_secret 2024-06-24 20:08:02 +02:00
ff1863f37e Merge pull request 'clan-cli: Add validity check for ssh' (#1652) from Qubasa/clan-core:Qubasa-test_secrets into main 2024-06-24 17:26:34 +00:00
eac869dde5 Fix run_vm test
nix fmt
2024-06-24 19:23:16 +02:00
88f97bd2b6 Fixed Contributing.md guide 2024-06-24 19:22:24 +02:00
fdd7ac7bbf Add repro-hook to .gitignore 2024-06-24 19:22:24 +02:00
8038a9b488 clan-cli: Add validity check for ssh
stash
2024-06-24 19:22:19 +02:00
37311f8145 Merge pull request 'Inventory: add concrete use-case examples' (#1650) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-24 14:03:46 +00:00
d7dc66da03
Json-schema tests: add schema specifier to test 2024-06-24 16:00:46 +02:00
51154c1d54
schema improvements: add additionalProperties: false to ensure closed types 2024-06-24 15:55:48 +02:00
13c3169b41
lib: eval clan module as lib function 2024-06-24 15:47:25 +02:00
fd62efc745 Merge pull request 'dev report: backup' (#1651) from matrix into main 2024-06-24 13:08:51 +00:00
5575c5d214 first draft of the backup article 2024-06-24 15:05:37 +02:00
294c5548b9
Inventory: add concrete use-case examples 2024-06-24 14:35:41 +02:00
fd9ad38900 Merge pull request 'Automatic flake update - 2024-06-24T00:00+00:00' (#1648) from flake-update-2024-06-24 into main 2024-06-24 00:06:47 +00:00
Clan Merge Bot
21e9945c97 update flake lock - 2024-06-24T00:00+00:00
Flake lock file updates:

• Updated input 'disko':
    'github:nix-community/disko/1bbdb06f14e2621290b250e631cf3d8948e4d19b' (2024-06-09)
  → 'github:nix-community/disko/e1174d991944a01eaaa04bc59c6281edca4c0e6e' (2024-06-20)
• Updated input 'nixos-generators':
    'github:nix-community/nixos-generators/d14b286322c7f4f897ca4b1726ce38cb68596c94' (2024-05-20)
  → 'github:nix-community/nixos-generators/35c20ba421dfa5059e20e0ef2343c875372bdcf3' (2024-06-10)
• Updated input 'nixos-images':
    'github:nix-community/nixos-images/72771bd35f4e19e32d6f652528483b5e07fc317b' (2024-06-07)
  → 'github:nix-community/nixos-images/c1e6a5f7b08f1c9993de1cfc5f15f838bf783b88' (2024-06-20)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/7d916e720af6b2ca355e4d0cfb8e4f742c172239' (2024-06-09)
  → 'github:NixOS/nixpkgs/084f8df2f3ff80cdec6f515931036f63c5d2f36c' (2024-06-23)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/f0922ad001829b400f0160ba85b47d252fa3d925' (2024-06-09)
  → 'github:Mic92/sops-nix/5e2e9421e9ed2b918be0a441c4535cfa45e04811' (2024-06-23)
• Updated input 'treefmt-nix':
    'github:numtide/treefmt-nix/4fc1c45a5f50169f9f29f6a98a438fb910b834ed' (2024-06-08)
  → 'github:numtide/treefmt-nix/68eb1dc333ce82d0ab0c0357363ea17c31ea1f81' (2024-06-16)
2024-06-24 00:00:22 +00:00
f4283982b3 Merge pull request 'fix fake-etc build on machines without sandbox' (#1645) from fix-macos-deploy into main 2024-06-21 15:05:45 +00:00
6086f27263 fix fake-etc build on machines without sandbox
If we have no sandbox enabled or on macos with sandbox enabled, /etc
contains a lot more files than we actually want.
Instead of copying some random files, we now just create those files
ourself.
2024-06-21 17:01:40 +02:00
0dfa1d969f Merge pull request 'clan-cli: Add validity check for age key generation' (#1642) from Qubasa/clan-core:Qubasa-main into main 2024-06-21 13:18:34 +00:00
1ff58adcef clan-cli: Add validity check for age key generation 2024-06-21 15:07:53 +02:00
641ec7e097 Merge pull request 'clan-cli: Disable stack trace on KeyboardInterrupt' (#1641) from Qubasa/clan-core:Qubasa-main into main 2024-06-21 11:14:47 +00:00
8ee33950e6 clan-cli: Disable stack trace on KeyboardInterrupt 2024-06-21 13:11:33 +02:00
b3123b150f Merge pull request 'clan.core.state: wrap all commands in shell scripts' (#1639) from refactor-state into main 2024-06-20 16:20:31 +00:00
20b952b4cd fix dropping non-existing database 2024-06-19 18:00:51 +02:00
aa5ccfb8bd clanCore -> clan.core 2024-06-19 17:55:59 +02:00
ef9ed1ebea clan.core.state: wrap all commands in shell scripts
Otherwise we cannot execute them via ssh and also have nix store
dependencies.
2024-06-19 17:54:46 +02:00
117aed49e3 postgresql: don't prepend postgresql- for states 2024-06-19 17:38:31 +02:00
9bbf7f668a Merge pull request 'Inventory: add concrete use-case examples' (#1636) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-19 11:08:04 +00:00
afdfa6181b
Inventory: add concrete use-case examples 2024-06-19 13:04:10 +02:00
6c11e0ced7 Merge pull request 'UI: display block devices' (#1635) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-19 09:39:41 +00:00
399ce2e35c
UI: display block devices 2024-06-19 11:36:19 +02:00
e575c2e769 Merge pull request 'UI: display known network hosts' (#1633) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-19 09:11:01 +00:00
56b2347a30
UI: display known network hosts 2024-06-19 11:07:45 +02:00
70954acf3d Merge pull request 'Inventory: init draft ideas' (#1632) from hsjobeki/clan-core:hsjobeki-inventory into main 2024-06-19 07:50:12 +00:00
13aa60529f
Inventory: init draft ideas 2024-06-19 09:47:13 +02:00
7474f01193
Inventory: init draft ideas 2024-06-19 09:40:23 +02:00
bd9883baaf Merge pull request 'refactor: rename clanCore -> clan.core' (#1629) from DavHau/clan-core:DavHau-rename-clanCore into main 2024-06-18 11:35:57 +00:00
313db5643f refactor: rename clanCore -> clan.core 2024-06-18 18:32:40 +07:00
93a6d7a476 Merge pull request 'update matrix address' (#1631) from new-matrix-address into main 2024-06-17 13:32:07 +00:00
d221d90972 update matrix address 2024-06-17 15:26:23 +02:00
30fd5dcfb8 Merge pull request 'matrix-synapse: restart service on restore' (#1630) from clan-name into main 2024-06-17 12:30:03 +00:00
c79680344d fix restore if database does not exists 2024-06-17 14:21:45 +02:00
ad544a7d24 matrix-synapse: restart service on restore 2024-06-17 14:21:45 +02:00
1cd606b879 Merge pull request 'fix: remove IFD in nix flake show' (#1628) from DavHau/clan-core:DavHau-dave into main 2024-06-17 08:09:39 +00:00
39f74c0f52 fix: remove IFD in nix flake show 2024-06-17 15:06:25 +07:00
8feea28a19 Merge pull request 'API: init methods: hw_generate, dns discovery' (#1626) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-16 14:32:42 +00:00
b73246bdfd
API: init methods: hw_generate, dns discovery 2024-06-16 16:29:18 +02:00
36a418b6ac Merge pull request 'Docs: update machine hardware config instructions' (#1625) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-15 20:30:50 +00:00
43e8804eb4
Docs: update machine hardware config instructions 2024-06-15 22:27:46 +02:00
8790e5a0eb Merge pull request 'CLI: init hw-generate command' (#1624) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-15 19:35:56 +00:00
5e39514251
CLI: init hw-generate command 2024-06-15 21:31:23 +02:00
b28950f310 Merge pull request 'API: init op_key, improve seralisation & signature typing' (#1622) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-15 09:38:29 +00:00
3ebee252aa
Webview: init machine details 2024-06-15 11:35:15 +02:00
720fb4af63
Webview: minor improvements 2024-06-15 11:34:54 +02:00
af19950dfa
Webview: init global state for current clan path 2024-06-15 11:34:20 +02:00
149be249fa
Webview: init api event registry 2024-06-15 11:33:53 +02:00
0cf86806b2
API: mock echo op_key 2024-06-15 11:32:42 +02:00
cb847cab82
API: init op_key, improve seralisation & signature typing 2024-06-15 11:32:09 +02:00
a89fd31844 Merge pull request 'matrix-synapse: user creation fixes' (#1620) from matrix into main 2024-06-14 09:36:41 +00:00
870948306d postgres: handle restores without associated systemd service 2024-06-14 11:29:59 +02:00
ec49d1f844 container-driver: source nixos environment variables in test commands 2024-06-14 11:27:06 +02:00
e3d84a5daf matrix-synapse: use upstream patch to create users declarativly 2024-06-14 11:27:06 +02:00
79b5ad0754 matrix-synapse: use registration_shared_secret_path instead 2024-06-14 11:18:09 +02:00
24b0d72d96 matrix-synapse: fix user check 2024-06-14 11:18:09 +02:00
084cd8751f postgresql: move postRestoreCommand to a dedicated command
We need to call this command from the cli
2024-06-14 11:18:09 +02:00
3d77e0a3a9 Merge pull request 'fix: outside of direnv clan-li warns show-config deprecated' (#1619) from samrose/clan-core:sam/show-config into main
Reviewed-on: clan/clan-core#1619
Reviewed-by: kenji <aks.kenji@protonmail.com>
2024-06-14 08:10:29 +00:00
06bbae6d14 fix: trying run_no_stdout with original show-config 2024-06-13 18:44:50 -04:00
5f22493361 fix: formatting cli command correctly 2024-06-13 12:59:26 -04:00
56a4caf39b fix: outside of direnv clan-li warns this is going to be deprecated 2024-06-13 12:44:48 -04:00
83056f743d Merge pull request 'API: init icon resolve' (#1616) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-12 11:24:45 +00:00
6743ff96a9
API: init icon resolve 2024-06-12 13:21:39 +02:00
1f3c4f4ac3 Merge pull request 'vm.nix: fix typo in comment' (#1611) from DavHau/clan-core:DavHau-dave into main 2024-06-11 21:49:18 +00:00
7766829fb1 vm.nix: fix typo in comment 2024-06-11 14:44:08 -07:00
175b219246 Merge pull request 'API: improve type & class construction' (#1610) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-11 17:23:42 +00:00
48aee84547
Webview: add form handling for create clan 2024-06-11 19:20:40 +02:00
d587b326b5
API: improve type & class construction 2024-06-11 19:20:40 +02:00
ac099d9e6f Merge pull request 'Webview/API: init open clan workflow' (#1609) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-11 14:31:51 +00:00
913ab4627c
Webview: init 'open clan' workflow 2024-06-11 16:28:02 +02:00
be868ee107 Merge pull request 'matrix-synapse: add missing xkcdpass to password generator' (#1608) from matrix into main 2024-06-11 13:44:08 +00:00
36b1bb65af matrix-synapse: add missing xkcdpass to password generator 2024-06-11 15:40:58 +02:00
4a752bb951 Merge pull request 'matrix-synapse: drop drop security.wrappers' (#1607) from nixos-images-input into main
Reviewed-on: clan/clan-core#1607
2024-06-11 13:32:58 +00:00
3dabb4e89a matrix-synapse: drop drop security.wrappers
This was debug code and not meant for production.
2024-06-11 13:32:58 +00:00
e2474f4e66 Merge pull request 'matrix-synapse: don't require to set default users' (#1606) from Mic92-nixos-images-input into main 2024-06-11 13:18:37 +00:00
f4ee0b0387 Merge pull request 'drop nixos-stable' (#1605) from nixos-images-input into main 2024-06-11 13:13:48 +00:00
5df1f9f9d2 matrix-synapse: don't require to set default users 2024-06-11 15:12:41 +02:00
3368255473 drop nixos-stable 2024-06-11 15:03:57 +02:00
1cbb2d6aa4 Merge pull request 'matrix-synapse: add automatic user creation' (#1603) from synapse into main 2024-06-11 11:22:32 +00:00
bc0e0088a0 matrix-synapse: add automatic user creation 2024-06-11 13:19:18 +02:00
a6a9f763db Merge pull request 'api: refactor create flake into create clan' (#1602) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-11 10:15:44 +00:00
8dcb009e5b
api: refactor create flake into create clan 2024-06-11 12:11:29 +02:00
9f0f44b470 Merge pull request 'borbackup: set IdentitiesOnly=Yes' (#1601) from kenji/clan-core:identities into main
Reviewed-on: clan/clan-core#1601
2024-06-10 14:09:14 +00:00
67aa84760d borbackup: set IdentitiesOnly=Yes
Since `borgbackup` is run as root user it might try other ssh keys.
2024-06-10 14:09:14 +00:00
b05c937151 Merge pull request 'backups: extend tests to also check state.preBackupCommand' (#1600) from synapse into main 2024-06-10 14:01:53 +00:00
3322bbd681 backups: extend tests to also check state.preBackupCommand 2024-06-10 15:57:41 +02:00
a1acf0b05d Merge pull request 'Expand backup and restore capabilities w.r.t. postgresql.' (#1582) from synapse into main 2024-06-10 13:24:08 +00:00
66bdc61e3d borgbackup: move preBackupScript to a different systemd context 2024-06-10 15:17:46 +02:00
dd2bd2f989 Merge pull request 'test: extend minimal flake test with verification' (#1599) from DavHau/clan-core:DavHau-dave into main 2024-06-10 05:24:17 +00:00
6f18a5de92 test: extend minimal flake test with verification 2024-06-09 22:19:38 -07:00
1d542d4396 Merge pull request 'tests: add test for creating machine on minimal clan' (#1596) from DavHau/clan-core:DavHau-dave into main 2024-06-10 04:58:02 +00:00
07fb01d9db tests: add test for creating machine on minimal clan 2024-06-09 21:54:04 -07:00
8a5d4a0f8f Merge pull request 'Automatic flake update - 2024-06-10T00:00+00:00' (#1598) from flake-update-2024-06-10 into main 2024-06-10 00:05:18 +00:00
Clan Merge Bot
48069f99cd update flake lock - 2024-06-10T00:00+00:00
Flake lock file updates:

• Updated input 'disko':
    'github:nix-community/disko/0274af4c92531ebfba4a5bd493251a143bc51f3c' (2024-05-31)
  → 'github:nix-community/disko/1bbdb06f14e2621290b250e631cf3d8948e4d19b' (2024-06-09)
• Updated input 'nixos-images':
    'github:nix-community/nixos-images/47bfb55316e105390dd761e0b6e8e0be09462b67' (2024-05-30)
  → 'github:nix-community/nixos-images/72771bd35f4e19e32d6f652528483b5e07fc317b' (2024-06-07)
• Removed input 'nixos-images/nixos-2311'
• Added input 'nixos-images/nixos-stable':
    'github:NixOS/nixpkgs/0b8e7a1ae5a94da2e1ee3f3030a32020f6254105' (2024-06-05)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/6634a0509e9e81e980b129435fbbec518ab246d0' (2024-06-02)
  → 'github:NixOS/nixpkgs/7d916e720af6b2ca355e4d0cfb8e4f742c172239' (2024-06-09)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/ab2a43b0d21d1d37d4d5726a892f714eaeb4b075' (2024-06-02)
  → 'github:Mic92/sops-nix/f0922ad001829b400f0160ba85b47d252fa3d925' (2024-06-09)
• Updated input 'treefmt-nix':
    'github:numtide/treefmt-nix/3eb96ca1ae9edf792a8e0963cc92fddfa5a87706' (2024-06-01)
  → 'github:numtide/treefmt-nix/4fc1c45a5f50169f9f29f6a98a438fb910b834ed' (2024-06-08)
2024-06-10 00:00:22 +00:00
1eaf6cec39 Merge pull request 'gui-installer: fix maintainer name' (#1597) from kenji/clan-core:fix-name into main
Reviewed-on: clan/clan-core#1597
2024-06-09 12:49:14 +00:00
f0c9de9e50 gui-installer: fix maintainer name 2024-06-09 14:28:54 +02:00
ef42bcc525 Merge pull request 'templates: add minimal clan flake template for (G)UI' (#1595) from DavHau/clan-core:DavHau-dave into main 2024-06-09 00:05:02 +00:00
e7995ad344 templates: add minimal clan flake template for (G)UI 2024-06-08 17:00:18 -07:00
6e3c2506c9 Merge pull request 'Clan-cli/show: more detailed description' (#1594) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-08 16:28:36 +00:00
5473e2733c
Clan-cli/show: more detailed description 2024-06-08 18:24:54 +02:00
006a7044f1 Merge pull request 'Webview: add solid-toast feeback system' (#1593) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-08 16:17:59 +00:00
c647197b8c
Webview: add solid-toast feeback system 2024-06-08 18:14:15 +02:00
62735ebfe2 Merge pull request 'API: add show clan to retrieve the buildClan meta' (#1592) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-08 16:11:43 +00:00
8ff00fd8fe
API: include show into docs 2024-06-08 18:07:41 +02:00
bd586575b3
API: add show clan to retrieve the buildClan meta 2024-06-08 17:53:17 +02:00
f14f7368d7 Merge pull request 'API: add abstract open_file method, implement open_file' (#1591) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-08 15:09:35 +00:00
6adcd1fdf2
API: add abstract open_file method, implement open_file 2024-06-08 17:04:56 +02:00
6e99beb335 Merge pull request 'UI: add open clan button' (#1590) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-08 13:28:18 +00:00
6689d45a4f
UI: add open clan button 2024-06-08 15:24:18 +02:00
6d82a5851b Merge pull request 'api: list files' (#1589) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-08 13:05:52 +00:00
337ba1f8f6
api: list files 2024-06-08 15:01:53 +02:00
bf7b148592 Merge pull request 'clan: fix backup provider completions' (#1587) from kenji/clan-core:clan-complete-providers into main
Reviewed-on: clan/clan-core#1587
2024-06-07 19:16:13 +00:00
a7f724a804 clan: fix backup provider completions 2024-06-07 19:16:13 +00:00
7c06b65def Merge pull request 'gui-installer: calculate bugfix version from lastModifiedDate' (#1588) from DavHau/clan-core:DavHau-dave into main 2024-06-07 18:39:28 +00:00
7286c7250c gui-installer: calculate bugfix version from lastModifiedDate 2024-06-07 11:35:25 -07:00
4e841d3087 Merge pull request 'clan: remove very obvious comments' (#1586) from kenji/clan-core:clan/package/clean into main
Reviewed-on: clan/clan-core#1586
2024-06-07 13:20:23 +00:00
2ce704dd40 clan: remove very obvious comments
Remove some very obvious comments as to not lose meaning of the
comments.

We want comments that convey non-obvious behavior so they will be
actually read.
2024-06-07 14:45:53 +02:00
6279610691 Merge pull request 'syncthing: automatically add zt network ip to devices' (#1585) from kenji/clan-core:syncthing/add-ip into main
Reviewed-on: clan/clan-core#1585
2024-06-07 12:33:09 +00:00
297d53dac8 syncthing: automatically add zt network ip to devices 2024-06-07 14:25:21 +02:00
6f1300f819 Merge pull request 'clan: install shell completions for zsh' (#1584) from kenji/clan-core:enable-zsh-completions into main
Reviewed-on: clan/clan-core#1584
2024-06-07 07:57:40 +00:00
02a015a1b6 clan: install shell completions for zsh 2024-06-06 23:20:54 +02:00
5c11a30b46 backup: add a way to stop services before restoring a state. 2024-06-06 17:30:35 +02:00
0dc3b9f056 postgresql: add backup and restore 2024-06-06 14:28:50 +02:00
c0d8aaf73a postgresql: add new method to create users and databases 2024-06-06 14:07:56 +02:00
2a0019457d matrix-synapse: create with utf-8 encoding 2024-06-06 14:07:56 +02:00
6dec2a9222 add postgresql backup hooks 2024-06-06 13:27:30 +02:00
f71295e640 fix running cli without arguments 2024-06-06 13:27:30 +02:00
c1aedc5bb8 matrix-enable: drop enable option 2024-06-06 13:27:30 +02:00
d6a9f6d3f9 change clan url to gitea archive url 2024-06-06 11:11:48 +02:00
ba6840d978 matrix-synapse: create database with right collation also when postgresql already exists #1108 2024-06-06 11:11:48 +02:00
86b08258dd Merge pull request 'syncthing-remove-newline' (#1581) from kenji/clan-core:syncthing-remove-newline into main
Reviewed-on: clan/clan-core#1581
2024-06-06 08:34:08 +00:00
9ccff4ab2e syncthing: remove trailing newline 2024-06-06 08:34:08 +00:00
cf310be1c8 Merge pull request 'syncthing: update facts to new system' (#1580) from kenji/clan-core:syncthing/update into main
Reviewed-on: clan/clan-core#1580
2024-06-06 08:21:31 +00:00
d8e80bb0c8 syncthing: update facts to new system 2024-06-06 08:21:31 +00:00
9206182e15 Merge pull request 'modules/syncthing-static-peers: init' (#1579) from kenji/clan-core:init/static-syncthing into main
Reviewed-on: clan/clan-core#1579
2024-06-06 08:01:09 +00:00
d25eaa48d0 modules/syncthing-static-peers: init 2024-06-06 09:55:57 +02:00
5a2c91959a Merge pull request 'borbackup-static: impl' (#1577) from kenji/clan-core:modules/init/borbackup-static-impl into main
Reviewed-on: clan/clan-core#1577
2024-06-05 21:17:24 +00:00
193d54153d borbackup-static: impl
Implements sane defaults implementing borgbackup

Fixes: #1551
2024-06-05 21:17:24 +00:00
510634bc04 Merge pull request 'zerotier-static-peers: filter out non existing Ip' (#1576) from kenji/clan-core:fix/static-zerotier into main
Reviewed-on: clan/clan-core#1576
2024-06-05 17:52:59 +00:00
954f1fe605 zerotier-static-peers: filter out non existing Ip 2024-06-05 19:46:55 +02:00
764b53275f Merge pull request 'static-hosts: filter out non existing Ip's' (#1574) from kenji/clan-core:static-hosts-fix into main
Reviewed-on: clan/clan-core#1574
2024-06-05 15:32:57 +00:00
44fc1be270 static-hosts: filter out non existing Ip's 2024-06-05 17:28:35 +02:00
5ef170020d Merge pull request 'clan-cli: Fix passwordstore clan facts generate requiring CTRL+D for every secret' (#1573) from Qubasa/clan-core:Qubasa-main into main 2024-06-05 12:37:02 +00:00
5f7099fc89 clan-cli: Fix passwordstore clan facts generate requiring CTRL+D for every secret 2024-06-05 14:31:45 +02:00
fe08fef015 Merge pull request 'clan-cli: temporary_home, set XDG_RUNTIME_DIR' (#1571) from Qubasa/clan-core:Qubasa-main into main 2024-06-05 11:39:14 +00:00
edb744f654 Merge pull request 'clan: improve machine completions' (#1572) from kenji/clan-core:clan/dynamic-completions into main
Reviewed-on: clan/clan-core#1572
2024-06-05 11:38:50 +00:00
5ff5b46896 clan-cli: temporary_home, set XDG_RUNTIME_DIR 2024-06-05 13:34:17 +02:00
49e67ac46c clan: improve machine completions
In recent nix versions the `nix flake show` command busts the eval
cache, which made the function that used to be faster, slower.

On benchmarks the completion was around 180-200ms.
2024-06-05 13:32:23 +02:00
5024973896 Merge pull request 'Hsjobeki Main' (#1570) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-05 10:14:53 +00:00
7dce6ad6c4
clan-app: switch default view to webui 2024-06-05 12:10:26 +02:00
779229a907 Merge pull request 'clan-app: rename clan-vm-manager' (#1569) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-05 09:41:05 +00:00
af23ed027a Merge pull request 'clan: fix backup description' (#1567) from kenji/clan-core:clan/fix/backup-description into main
Reviewed-on: clan/clan-core#1567
2024-06-05 09:40:01 +00:00
06412865bb
clan-app: rename clan-vm-manager 2024-06-05 11:36:02 +02:00
fab311b53a clan: fix backup description 2024-06-05 11:35:04 +02:00
bc602dbf3c Merge pull request 'clan-app: Rename clan-vm-manager to clan-app.' (#1566) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-05 09:31:22 +00:00
0fb207bb59
clan-app: rename clan-vm-manager 2024-06-05 11:24:59 +02:00
c751bc78d8 Merge pull request 'clan: add dynamic completions to clan backups' (#1565) from kenji/clan-core:clan/backups/dynamic-completions into main
Reviewed-on: clan/clan-core#1565
2024-06-05 09:23:02 +00:00
c9038ad0b3 clan: add dynamic completions to clan backups providers 2024-06-05 09:23:02 +00:00
b4699cd8a3 clan: add dynamic completion function for backup providers 2024-06-05 09:23:02 +00:00
d0a87d8e3c Merge pull request 'web-ui: init type API checks' (#1564) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-05 09:11:44 +00:00
78dbabf901
web-ui: init type API checks 2024-06-05 11:07:55 +02:00
ad771ae6a0 Merge pull request 'web-ui: remove type test method' (#1563) from hsjobeki/clan-core:hsjobeki-main into main 2024-06-05 07:58:08 +00:00
92bc2962b8
web-ui: remove type test method 2024-06-05 09:54:17 +02:00
836754d7ad Merge pull request 'hsjobeki-main' (#1562) from hsjobeki-main into main 2024-06-05 07:52:38 +00:00
6576290160
clan-api: wrap all api responses with error/success envelop type 2024-06-05 09:46:48 +02:00
db88e63148
clan-cli: create flake refactor to create clan 2024-06-05 09:45:47 +02:00
f2d2102127
clan-cli: refactor CmdOut to serializable dataclass 2024-06-05 09:44:44 +02:00
b9bf453731
clan-api: add support for generic types & annotated types 2024-06-05 09:43:45 +02:00
fb98247a8d Merge pull request 'demo.sh: fix tags' (#1561) from DavHau/clan-core:DavHau-dave into main 2024-06-04 22:27:39 +00:00
4bd927cbcf demo.sh: fix tags 2024-06-04 15:23:39 -07:00
3725d5703e Merge pull request 'documentation(backups): make exampe more robust' (#1560) from kenji/clan-core:improve/backup-docs into main
Reviewed-on: clan/clan-core#1560
2024-06-04 15:17:29 +00:00
bf0cc19c8f documentation(backups): make exampe more robust 2024-06-04 17:09:26 +02:00
8af137545f Merge pull request 'borgbackup: fixes error when specifying multiple backup directories' (#1559) from kenji/clan-core:improve/backup-docs into main
Reviewed-on: clan/clan-core#1559
2024-06-04 14:54:39 +00:00
3d71ebcc5f borgbackup: try to fix 2024-06-04 16:27:53 +02:00
c6fcb833b3 Merge pull request 'clan/improve/dynamic-completions-config' (#1558) from kenji/clan-core:clan/improve/dynamic-completions-config into main
Reviewed-on: clan/clan-core#1558
2024-06-04 13:57:18 +00:00
c926f23c09 clan: add dynamic completions to clan vms inspect 2024-06-04 15:50:50 +02:00
21ac1f7204 clan: add dynamic completions to clan vms run 2024-06-04 15:48:12 +02:00
05ff7bd261 clan: add dynamic completions to clan config 2024-06-04 15:44:39 +02:00
b2109351ff Merge pull request 'clan/improve/dynamic-completions' (#1557) from kenji/clan-core:clan/improve/dynamic-completions into main
Reviewed-on: clan/clan-core#1557
2024-06-04 13:28:09 +00:00
0bd13727de clan: add dynamic-completions to clan secrets set 2024-06-04 15:21:00 +02:00
e1d6d04b48 clan: add dynamic completions to clan secrets machines 2024-06-04 15:02:35 +02:00
9dbbb6f2f6 clan: add dynamic completions for clan secrets import-sops 2024-06-04 13:40:24 +02:00
836170e5b6 Merge pull request 'clan: add dynamic completions to clan secrets {users,groups} and add completion functions' (#1556) from kenji/clan-core:add/completion/to-groups into main
Reviewed-on: clan/clan-core#1556
2024-06-04 11:35:42 +00:00
d4fabff7f4 clan: add dynamic completions for secret groups 2024-06-04 13:30:38 +02:00
b21bef0b98 clan: add dynamic completions for clan secrets users 2024-06-04 13:30:38 +02:00
533ed97fc1 clan: add dynamic completion for clan secret groups 2024-06-04 13:30:38 +02:00
e7e5a1ded8 clan: add completion function for clan users 2024-06-04 13:30:11 +02:00
4e95030e55 clan: clan secrets groups add machine completions 2024-06-04 13:30:11 +02:00
b331a8c730 Merge pull request 'clan: fix help message' (#1553) from kenji/clan-core:add/completion/groups into main
Reviewed-on: clan/clan-core#1553
2024-06-04 10:28:34 +00:00
2923051a12 clan: fix help message 2024-06-04 10:28:34 +00:00
fe96137c56 Merge pull request 'clan: add more machine completion functions to secrets' (#1552) from kenji/clan-core:add/completion/secret-subcommands into main
Reviewed-on: clan/clan-core#1552
2024-06-04 09:30:02 +00:00
addc4de735 clan: add more machine completion functions to secrets 2024-06-04 11:07:24 +02:00
2460ba9b67 Merge pull request 'demo.sh: fix tag' (#1550) from DavHau/clan-core:DavHau-dave into main 2024-06-04 05:00:07 +00:00
62be27ec62 demo.sh: fix tag 2024-06-03 21:55:09 -07:00
8515d41fe3 Merge pull request 'distro-packages: add test for deb installation' (#1549) from DavHau/clan-core:DavHau-dave into main 2024-06-04 04:17:21 +00:00
d4d69d6990 distro-packages: add test for deb installation 2024-06-04 06:09:58 +02:00
0027c46313 Merge pull request 'zerotier-static-peers: use correct exclusion source' (#1548) from kenji/clan-core:modules/fix/static into main
Reviewed-on: clan/clan-core#1548
2024-06-03 21:24:29 +00:00
ca2001040b zerotier-static-peers: use correct exclusion source 2024-06-03 22:53:44 +02:00
d6725100ac Merge pull request 'zerotier-static-peers: add guard condition' (#1547) from kenji/clan-core:modules/add/zerotier-guard into main
Reviewed-on: clan/clan-core#1547
2024-06-03 20:47:03 +00:00
503ce29c84 zerotier-static-peers: add guard condition 2024-06-03 22:42:04 +02:00
87444cd2b8 Merge pull request 'clan: add dyncamic completions for secrets' (#1546) from kenji/clan-core:kenji-clan/secrets-dynamic/add-completion into main
Reviewed-on: clan/clan-core#1546
2024-06-03 19:55:12 +00:00
31eca9e8bc clan: add dyncamic completions for secrets 2024-06-03 21:47:14 +02:00
822afe08b5 Merge pull request 'clan: add dynamic machine completions to clan secrets subcommands' (#1545) from clan/secrets/add-completions into main
Reviewed-on: clan/clan-core#1545
2024-06-03 15:42:37 +00:00
cfb78b0edb clan: add dynamic machine completions to clan secrets subcommands 2024-06-03 17:32:33 +02:00
65fd7d3efe Merge pull request 'clan: add dynamic completion to clan machines show' (#1544) from kenji-clan/machine-show/add-commpletion into main
Reviewed-on: clan/clan-core#1544
2024-06-03 15:15:45 +00:00
e8241fb7c9 clan: add dynamic completion to clan machines show 2024-06-03 17:06:03 +02:00
290 changed files with 8404 additions and 1432 deletions

3
.gitignore vendored
View File

@ -3,6 +3,7 @@
out.log
.coverage.*
**/qubeclan
pkgs/repro-hook
**/testdir
democlan
example_clan
@ -35,4 +36,4 @@ repo
# node
node_modules
dist
.webui
.webui

View File

@ -1,6 +1,6 @@
# Clan Core Repository
# Clan core repository
Welcome to the Clan Core Repository, the heart of the [clan.lol](https://clan.lol/) project! This monorepo is the foundation of Clan, a revolutionary open-source project aimed at restoring fun, freedom, and functionality to computing. Here, you'll find all the essential packages, NixOS modules, CLI tools, and tests needed to contribute to and work with the Clan project. Clan leverages the Nix system to ensure reliability, security, and seamless management of digital environments, putting the power back into the hands of users.
Welcome to the Clan core repository, the heart of the [clan.lol](https://clan.lol/) project! This monorepo is the foundation of Clan, a revolutionary open-source project aimed at restoring fun, freedom, and functionality to computing. Here, you'll find all the essential packages, NixOS modules, CLI tools, and tests needed to contribute to and work with the Clan project. Clan leverages the Nix system to ensure reliability, security, and seamless management of digital environments, putting the power back into the hands of users.
## Why Clan?
@ -14,13 +14,13 @@ Our mission is simple: to democratize computing by providing tools that empower
- **Robust Backup Management:** Long-term, self-hosted data preservation.
- **Intuitive Secret Management:** Simplified encryption and password management processes.
## Getting Started with Clan
## Getting started with Clan
If you're new to Clan and eager to dive in, start with our quickstart guide and explore the core functionalities that Clan offers:
- **Quickstart Guide**: Check out [getting started](https://docs.clan.lol/#starting-with-a-new-clan-project)<!-- [docs/site/index.md](docs/site/index.md) --> to get up and running with Clan in no time.
### Managing Secrets
### Managing secrets
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
@ -32,14 +32,14 @@ The Clan project thrives on community contributions. We welcome everyone to cont
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
## Join the Revolution
## Join the revolution
Clan is more than a tool; it's a movement towards a better digital future. By contributing to the Clan project, you're part of changing technology for the better, together.
### Community and Support
### 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.
- [Matrix channel](https://matrix.to/#/#clan:clan.lol) for live discussions.
- IRC bridges (coming soon) for real-time chat support.

View File

@ -68,17 +68,9 @@
};
};
};
clanCore.facts.secretStore = "vm";
clan.core.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.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
environment.etc.install-closure.source = "${closureInfo}/store-paths";
nix.settings = {
substituters = lib.mkForce [ ];
@ -87,11 +79,18 @@
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
};
system.extraDependencies = dependencies;
clanCore.state.test-backups.folders = [ "/var/test-backups" ];
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
clanCore.state.test-service = {
preRestoreCommand = "pre-restore-command";
postRestoreCommand = "post-restore-command";
clan.core.state.test-service = {
preBackupScript = ''
touch /var/test-service/pre-backup-command
'';
preRestoreScript = ''
touch /var/test-service/pre-restore-command
'';
postRestoreScript = ''
touch /var/test-service/post-restore-command
'';
folders = [ "/var/test-service" ];
};
clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";
@ -164,13 +163,15 @@
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")
machine.succeed("test -f /var/test-service/pre-backup-command")
## localbackup restore
machine.succeed("rm -f /var/test-backups/somefile /var/test-service/{pre,post}-restore-command")
machine.succeed("rm -rf /var/test-backups/somefile /var/test-service/ && mkdir -p /var/test-service")
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")
machine.succeed("test -f /var/test-service/pre-backup-command")
'';
} { inherit pkgs self; };
};

View File

@ -16,9 +16,9 @@
};
}
{
clanCore.machineName = "machine";
clanCore.clanDir = ./.;
clanCore.state.testState.folders = [ "/etc/state" ];
clan.core.machineName = "machine";
clan.core.clanDir = ./.;
clan.core.state.testState.folders = [ "/etc/state" ];
environment.etc.state.text = "hello world";
systemd.tmpfiles.settings."vmsecrets" = {
"/etc/secrets/borgbackup.ssh" = {
@ -36,7 +36,7 @@
};
};
};
clanCore.facts.secretStore = "vm";
clan.core.facts.secretStore = "vm";
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
}

View File

@ -10,8 +10,8 @@
self.clanModules.deltachat
self.nixosModules.clanCore
{
clanCore.machineName = "machine";
clanCore.clanDir = ./.;
clan.core.machineName = "machine";
clan.core.clanDir = ./.;
}
];
};

View File

@ -23,7 +23,7 @@
options =
(pkgs.nixos {
imports = [ self.nixosModules.clanCore ];
clanCore.clanDir = ./.;
clan.core.clanDir = ./.;
}).options;
warningsAreErrors = false;
};
@ -44,6 +44,7 @@
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
borgbackup = import ./borgbackup nixosTestArgs;
syncthing = import ./syncthing nixosTestArgs;
postgresql = import ./postgresql nixosTestArgs;
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
};

View File

@ -12,7 +12,7 @@
{ lib, modulesPath, ... }:
{
imports = [
self.clanModules.disk-layouts
"${self}/nixosModules/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")
];

View File

@ -151,7 +151,7 @@ class Machine:
"""
# Always run command with shell opts
command = f"set -euo pipefail; {command}"
command = f"set -eo pipefail; source /etc/profile; set -u; {command}"
proc = subprocess.run(
[

View File

@ -4,26 +4,61 @@
name = "matrix-synapse";
nodes.machine =
{ self, lib, ... }:
{
config,
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}";
clan.core.machineName = "machine";
clan.core.clanDir = ./.;
services.nginx.virtualHosts."matrix.clan.test" = {
enableACME = lib.mkForce false;
forceSSL = lib.mkForce false;
};
clan.matrix-synapse.domain = "clan.test";
clan.matrix-synapse.users.admin.admin = true;
clan.matrix-synapse.users.someuser = { };
clan.core.facts.secretStore = "vm";
# because we use systemd-tmpfiles to copy the secrets, we need to a seperate systemd-tmpfiles call to provison them.
boot.postBootCommands = "${config.systemd.package}/bin/systemd-tmpfiles --create /etc/tmpfiles.d/00-vmsecrets.conf";
systemd.tmpfiles.settings."00-vmsecrets" = {
# run before 00-nixos.conf
"/etc/secrets" = {
d.mode = "0700";
z.mode = "0700";
};
"/etc/secrets/synapse-registration_shared_secret" = {
f.argument = "supersecret";
z = {
mode = "0400";
user = "root";
};
};
"/etc/secrets/matrix-password-admin" = {
f.argument = "matrix-password1";
z = {
mode = "0400";
user = "root";
};
};
"/etc/secrets/matrix-password-someuser" = {
f.argument = "matrix-password2";
z = {
mode = "0400";
user = "root";
};
};
};
}
];
};
@ -32,6 +67,12 @@
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'")
machine.systemctl("restart matrix-synapse >&2") # check if user creation is idempotent
machine.execute("journalctl -u matrix-synapse --no-pager >&2")
machine.wait_for_unit("matrix-synapse")
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
'';
}
)

View File

@ -0,0 +1,72 @@
(import ../lib/container-test.nix) ({
name = "postgresql";
nodes.machine =
{ self, config, ... }:
{
imports = [
self.nixosModules.clanCore
self.clanModules.postgresql
self.clanModules.localbackup
];
clan.postgresql.users.test = { };
clan.postgresql.databases.test.create.options.OWNER = "test";
clan.postgresql.databases.test.restore.stopOnRestore = [ "sample-service" ];
clan.localbackup.targets.hdd.directory = "/mnt/external-disk";
systemd.services.sample-service = {
wantedBy = [ "multi-user.target" ];
script = ''
while true; do
echo "Hello, world!"
sleep 5
done
'';
};
environment.systemPackages = [ config.services.postgresql.package ];
};
testScript =
{ nodes, ... }:
''
start_all()
machine.wait_for_unit("postgresql")
machine.wait_for_unit("sample-service")
# Create a test table
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -c 'CREATE TABLE test (id serial PRIMARY KEY);' test")
machine.succeed("/run/current-system/sw/bin/localbackup-create >&2")
timestamp_before = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
machine.succeed("test -e /mnt/external-disk/snapshot.0/machine/var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'INSERT INTO test DEFAULT VALUES;'")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'DROP TABLE test;'")
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
machine.succeed("rm -rf /var/backup/postgres")
machine.succeed("NAME=/mnt/external-disk/snapshot.0 FOLDERS=/var/backup/postgres/test /run/current-system/sw/bin/localbackup-restore >&2")
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
machine.succeed("""
set -x
${nodes.machine.clan.core.state.test.postRestoreCommand}
""")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -l >&2")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
timestamp_after = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
assert timestamp_before < timestamp_after, f"{timestamp_before} >= {timestamp_after}: expected sample-service to be restarted after restore"
# Check that the table is still there
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'SELECT * FROM test;'")
output = machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql --csv -c \"SELECT datdba::regrole FROM pg_database WHERE datname = 'test'\"")
owner = output.split("\n")[1]
assert owner == "test", f"Expected database owner to be 'test', got '{owner}'"
# check if restore works if the database does not exist
machine.succeed("runuser -u postgres -- dropdb test")
machine.succeed("${nodes.machine.clan.core.state.test.postRestoreCommand}")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
'';
})

View File

@ -10,8 +10,8 @@
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
sops.age.keyFile = "/etc/privkey.age";
clanCore.clanDir = "${./.}";
clanCore.machineName = "machine";
clan.core.clanDir = "${./.}";
clan.core.machineName = "machine";
networking.hostName = "machine";
};

View File

@ -12,14 +12,14 @@
self.clanModules.syncthing
self.nixosModules.clanCore
{
clanCore.machineName = "introducer";
clanCore.clanDir = ./.;
clan.core.machineName = "introducer";
clan.core.clanDir = ./.;
environment.etc = {
"syncthing.pam".source = ./introducer/introducer_test_cert;
"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";
clan.core.facts.services.syncthing.secret."syncthing.api".path = "/etc/syncthing.api";
services.syncthing.cert = "/etc/syncthing.pam";
services.syncthing.key = "/etc/syncthing.key";
# Doesn't test zerotier!
@ -53,8 +53,8 @@
self.clanModules.syncthing
self.nixosModules.clanCore
{
clanCore.machineName = "peer1";
clanCore.clanDir = ./.;
clan.core.machineName = "peer1";
clan.core.clanDir = ./.;
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
builtins.readFile ./introducer/introducer_device_id
);
@ -75,8 +75,8 @@
self.clanModules.syncthing
self.nixosModules.clanCore
{
clanCore.machineName = "peer2";
clanCore.clanDir = ./.;
clan.core.machineName = "peer2";
clan.core.clanDir = ./.;
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
builtins.readFile ./introducer/introducer_device_id
);

View File

@ -14,8 +14,8 @@ import ../lib/test-base.nix (
imports = [
self.nixosModules.clanCore
{
clanCore.machineName = "machine";
clanCore.clanDir = ./.;
clan.core.machineName = "machine";
clan.core.clanDir = ./.;
}
];
services.wayland-proxy-virtwl.enable = true;

View File

@ -10,8 +10,8 @@
self.nixosModules.clanCore
self.clanModules.zt-tcp-relay
{
clanCore.machineName = "machine";
clanCore.clanDir = ./.;
clan.core.machineName = "machine";
clan.core.clanDir = ./.;
}
];
};

View File

@ -0,0 +1,11 @@
---
description = "Statically configure borgbackup with sane defaults."
---
This module implements the `borgbackup` backend and implements sane defaults
for backup management through `borgbackup` for members of the clan.
Configure target machines where the backups should be sent to through `targets`.
Configure machines that should be backuped either through `includeMachines`
which will exclusively add the included machines to be backuped, or through
`excludeMachines`, which will add every machine except the excluded machine to the backup.

View File

@ -0,0 +1,101 @@
{ lib, config, ... }:
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 = {
excludeMachines = lib.mkOption {
type = lib.types.listOf lib.types.str;
example = [ config.clan.core.machineName ];
default = [ ];
description = ''
Machines that should not be backuped.
Mutually exclusive with includeMachines.
If this is not empty, every other machine except the targets in the clan will be backuped by this module.
If includeMachines is set, only the included machines will be backuped.
'';
};
includeMachines = lib.mkOption {
type = lib.types.listOf lib.types.str;
example = [ config.clan.core.machineName ];
default = [ ];
description = ''
Machines that should be backuped.
Mutually exclusive with excludeMachines.
'';
};
targets = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Machines that should act as target machines for backups.
'';
};
};
config.services.borgbackup.repos =
let
machines = builtins.readDir machineDir;
borgbackupIpMachinePath = machines: machineDir + machines + "/facts/borgbackup.ssh.pub";
filteredMachines =
if ((builtins.length config.clan.borgbackup-static.includeMachines) != 0) then
lib.filterAttrs (name: _: (lib.elem name config.clan.borgbackup-static.includeMachines)) machines
else
lib.filterAttrs (name: _: !(lib.elem name config.clan.borgbackup-static.excludeMachines)) machines;
machinesMaybeKey = lib.mapAttrsToList (
machine: _:
let
fullPath = borgbackupIpMachinePath machine;
in
if builtins.pathExists fullPath then machine else null
) filteredMachines;
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
hosts = builtins.map (machine: {
name = machine;
value = {
path = "/var/lib/borgbackup/${machine}";
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
};
}) machinesWithKey;
in
lib.mkIf
(builtins.any (
target: target == config.clan.core.machineName
) config.clan.borgbackup-static.targets)
(if (builtins.listToAttrs hosts) != null then builtins.listToAttrs hosts else { });
config.clan.borgbackup.destinations =
let
destinations = builtins.map (d: {
name = d;
value = {
repo = "borg@${d}:/var/lib/borgbackup/${config.clan.core.machineName}";
};
}) config.clan.borgbackup-static.targets;
in
lib.mkIf (builtins.any (
target: target == config.clan.core.machineName
) config.clan.borgbackup-static.includeMachines) (builtins.listToAttrs destinations);
config.assertions = [
{
assertion =
!(
((builtins.length config.clan.borgbackup-static.excludeMachines) != 0)
&& ((builtins.length config.clan.borgbackup-static.includeMachines) != 0)
);
message = ''
The options:
config.clan.borgbackup-static.excludeMachines = [${builtins.toString config.clan.borgbackup-static.excludeMachines}]
and
config.clan.borgbackup-static.includeMachines = [${builtins.toString config.clan.borgbackup-static.includeMachines}]
are mutually exclusive.
Use excludeMachines to exclude certain machines and backup the other clan machines.
Use include machines to only backup certain machines.
'';
}
];
}

View File

@ -1,2 +1,13 @@
Efficient, deduplicating backup program with optional compression and secure encryption.
---
---
description = "Efficient, deduplicating backup program with optional compression and secure encryption."
categories = ["backup"]
---
BorgBackup (short: Borg) gives you:
- Space efficient storage of backups.
- Secure, authenticated encryption.
- Compression: lz4, zstd, zlib, lzma or none.
- Mountable backups with FUSE.
- Easy installation on multiple platforms: Linux, macOS, BSD, ...
- Free software (BSD license).
- Backed by a large and active open source community.

View File

@ -6,8 +6,73 @@
}:
let
cfg = config.clan.borgbackup;
preBackupScript = ''
declare -A preCommandErrors
${lib.concatMapStringsSep "\n" (
state:
lib.optionalString (state.preBackupCommand != null) ''
echo "Running pre-backup command for ${state.name}"
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
preCommandErrors["${state.name}"]=1
fi
''
) (lib.attrValues config.clan.core.state)}
if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then
echo "pre-backup commands failed for the following services:"
for state in "''${!preCommandErrors[@]}"; do
echo " $state"
done
exit 1
fi
'';
in
# Each .nix file in the roles directory is a role
# TODO: Helper function to set available roles within module meta.
# roles =
# if builtins.pathExists ./roles then
# lib.pipe ./roles [
# builtins.readDir
# (lib.filterAttrs (_n: v: v == "regular"))
# lib.attrNames
# (map (fileName: lib.removeSuffix ".nix" fileName))
# ]
# else
# null;
# TODO: make this an interface of every module
# Maybe load from readme.md
# metaInfoOption = lib.mkOption {
# readOnly = true;
# description = ''
# Meta is used to retrieve information about this module.
# - `availableRoles` is a list of roles that can be assigned via the inventory.
# - `category` is used to group services in the clan marketplace.
# - `description` is a short description of the service for the clan marketplace.
# '';
# default = {
# description = "Borgbackup is a backup program. Optionally, it supports compression and authenticated encryption.";
# availableRoles = roles;
# category = "backup";
# };
# type = lib.types.submodule {
# options = {
# description = lib.mkOption { type = lib.types.str; };
# availableRoles = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); };
# category = lib.mkOption {
# description = "A category for the service. This is used to group services in the clan ui";
# type = lib.types.enum [
# "backup"
# "network"
# ];
# };
# };
# };
# };
{
# options.clan.borgbackup.meta = metaInfoOption;
options.clan.borgbackup.destinations = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
@ -26,9 +91,9 @@ in
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";
config.clan.core.facts.services.borgbackup.secret."borgbackup.ssh".path
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
defaultText = "ssh -i \${config.clan.core.facts.services.borgbackup.secret.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
description = "the rsh to use for the backup";
};
};
@ -50,21 +115,30 @@ in
];
config = lib.mkIf (cfg.destinations != { }) {
systemd.services = lib.mapAttrs' (
_: dest:
lib.nameValuePair "borgbackup-job-${dest.name}" {
# since borgbackup mounts the system read-only, we need to run in a ExecStartPre script, so we can generate additional files.
serviceConfig.ExecStartPre = [
''+${pkgs.writeShellScript "borgbackup-job-${dest.name}-pre-backup-commands" preBackupScript}''
];
}
) cfg.destinations;
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
paths = lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state));
paths = lib.unique (
lib.flatten (map (state: state.folders) (lib.attrValues config.clan.core.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}";
passCommand = "cat ${config.clan.core.facts.services.borgbackup.secret."borgbackup.repokey".path}";
};
prune.keep = {
@ -75,7 +149,7 @@ in
};
}) cfg.destinations;
clanCore.facts.services.borgbackup = {
clan.core.facts.services.borgbackup = {
public."borgbackup.ssh.pub" = { };
secret."borgbackup.ssh" = { };
secret."borgbackup.repokey" = { };
@ -111,7 +185,7 @@ in
(pkgs.writeShellScriptBin "borgbackup-restore" ''
set -efux
cd /
IFS=';' read -ra FOLDER <<< "$FOLDERS"
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
@ -122,7 +196,7 @@ in
'')
];
clanCore.backups.providers.borgbackup = {
clan.core.backups.providers.borgbackup = {
list = "borgbackup-list";
create = "borgbackup-create";
restore = "borgbackup-restore";

View 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);
}

View 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 { };
}

View File

@ -1,4 +1,5 @@
Email-based instant messaging for Desktop.
---
description = "Email-based instant messaging for Desktop."
---
!!! warning "Under construction"

View File

@ -5,7 +5,7 @@
services.maddy =
let
domain = "${config.clanCore.machineName}.local";
domain = "${config.clan.core.machineName}.local";
in
{
enable = true;

View File

@ -1,2 +0,0 @@
Automatically format a disk drive on clan installation
---

View File

@ -1,2 +1,3 @@
A modern IRC server
---
description = "A modern IRC server"
---

View File

@ -10,5 +10,5 @@ _: {
};
};
clanCore.state.ergochat.folders = [ "/var/lib/ergo" ];
clan.core.state.ergochat.folders = [ "/var/lib/ergo" ];
}

View File

@ -1,21 +1,21 @@
{ ... }:
{
flake.clanModules = {
disk-layouts = {
imports = [ ./disk-layouts ];
};
borgbackup = ./borgbackup;
borgbackup-static = ./borgbackup-static;
deltachat = ./deltachat;
ergochat = ./ergochat;
localbackup = ./localbackup;
localsend = ./localsend;
matrix-synapse = ./matrix-synapse;
moonlight = ./moonlight;
postgresql = ./postgresql;
root-password = ./root-password;
sshd = ./sshd;
sunshine = ./sunshine;
static-hosts = ./static-hosts;
syncthing = ./syncthing;
syncthing-static-peers = ./syncthing-static-peers;
thelounge = ./thelounge;
trusted-nix-caches = ./trusted-nix-caches;
user-password = ./user-password;

View File

@ -1,2 +1,3 @@
Automatically backups current machine to local directory.
---
description = "Automatically backups current machine to local directory."
---

View File

@ -6,7 +6,10 @@
}:
let
cfg = config.clan.localbackup;
rsnapshotConfig = target: states: ''
uniqueFolders = lib.unique (
lib.flatten (lib.mapAttrsToList (_name: state: state.folders) config.clan.core.state)
);
rsnapshotConfig = target: ''
config_version 1.2
snapshot_root ${target.directory}
sync_first 1
@ -17,12 +20,6 @@ let
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" ''
@ -31,11 +28,9 @@ let
''}
''}
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
${lib.concatMapStringsSep "\n" (state: ''
${lib.concatMapStringsSep "\n" (folder: ''
backup ${folder} ${config.networking.hostName}/
'') state.folders}
'') states}
${lib.concatMapStringsSep "\n" (folder: ''
backup ${folder} ${config.networking.hostName}/
'') uniqueFolders}
'';
in
{
@ -129,14 +124,29 @@ in
]
}
${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)}
'')
${mountHook target}
echo "Creating backup '${target.name}'"
${lib.optionalString (target.preBackupHook != null) ''
(
${target.preBackupHook}
)
''}
declare -A preCommandErrors
${lib.concatMapStringsSep "\n" (
state:
lib.optionalString (state.preBackupCommand != null) ''
echo "Running pre-backup command for ${state.name}"
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
preCommandErrors["${state.name}"]=1
fi
''
) (builtins.attrValues config.clan.core.state)}
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" sync
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" snapshot
'') (builtins.attrValues cfg.targets)}'')
(pkgs.writeShellScriptBin "localbackup-list" ''
set -efu -o pipefail
export PATH=${
@ -167,6 +177,14 @@ in
pkgs.gawk
]
}
if [[ "''${NAME:-}" == "" ]]; then
echo "No backup name given via NAME environment variable"
exit 1
fi
if [[ "''${FOLDERS:-}" == "" ]]; then
echo "No folders given via FOLDERS environment variable"
exit 1
fi
name=$(awk -F'::' '{print $1}' <<< $NAME)
backupname=''${NAME#$name::}
@ -182,8 +200,9 @@ in
exit 1
fi
IFS=';' read -ra FOLDER <<< "$FOLDERS"
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
for folder in "''${FOLDER[@]}"; do
mkdir -p "$folder"
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
done
'')
@ -213,7 +232,7 @@ in
''
) cfg.targets;
clanCore.backups.providers.localbackup = {
clan.core.backups.providers.localbackup = {
# TODO list needs to run locally or on the remote machine
list = "localbackup-list";
create = "localbackup-create";

View File

@ -1,2 +1,3 @@
Securely sharing files and messages over a local network without internet connectivity.
---
description = "Securely sharing files and messages over a local network without internet connectivity."
---

View File

@ -18,7 +18,7 @@
};
config = lib.mkIf config.clan.localsend.enable {
clanCore.state.localsend.folders = [
clan.core.state.localsend.folders = [
"/var/localsend"
config.clan.localsend.defaultLocation
];

View File

@ -0,0 +1,100 @@
From bc199a27f23b0fcf175b116f7cf606c0d22b422a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io>
Date: Tue, 11 Jun 2024 11:40:47 +0200
Subject: [PATCH 1/2] register_new_matrix_user: add password-file flag
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
getpass in python expects stdin to be a tty, hence we cannot just pipe
into register_new_matrix_user. --password-file instead works better and
it would also allow the use of stdin if /dev/stdin is passed.
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
---
changelog.d/17294.feature | 2 ++
debian/register_new_matrix_user.ronn | 9 +++++++--
synapse/_scripts/register_new_matrix_user.py | 20 +++++++++++++++-----
3 files changed, 24 insertions(+), 7 deletions(-)
create mode 100644 changelog.d/17294.feature
diff --git a/changelog.d/17294.feature b/changelog.d/17294.feature
new file mode 100644
index 000000000..33aac7b0b
--- /dev/null
+++ b/changelog.d/17294.feature
@@ -0,0 +1,2 @@
+`register_new_matrix_user` now supports a --password-file flag, which
+is useful for scripting.
diff --git a/debian/register_new_matrix_user.ronn b/debian/register_new_matrix_user.ronn
index 0410b1f4c..d99e9215a 100644
--- a/debian/register_new_matrix_user.ronn
+++ b/debian/register_new_matrix_user.ronn
@@ -31,8 +31,13 @@ A sample YAML file accepted by `register_new_matrix_user` is described below:
Local part of the new user. Will prompt if omitted.
* `-p`, `--password`:
- New password for user. Will prompt if omitted. Supplying the password
- on the command line is not recommended. Use the STDIN instead.
+ New password for user. Will prompt if this option and `--password-file` are omitted.
+ Supplying the password on the command line is not recommended.
+ Use `--password-file` if possible.
+
+ * `--password-file`:
+ File containing the new password for user. If set, overrides `--password`.
+ This is a more secure alternative to specifying the password on the command line.
* `-a`, `--admin`:
Register new user as an admin. Will prompt if omitted.
diff --git a/synapse/_scripts/register_new_matrix_user.py b/synapse/_scripts/register_new_matrix_user.py
index 77a7129ee..972b35e2d 100644
--- a/synapse/_scripts/register_new_matrix_user.py
+++ b/synapse/_scripts/register_new_matrix_user.py
@@ -173,11 +173,18 @@ def main() -> None:
default=None,
help="Local part of the new user. Will prompt if omitted.",
)
- parser.add_argument(
+ password_group = parser.add_mutually_exclusive_group()
+ password_group.add_argument(
"-p",
"--password",
default=None,
- help="New password for user. Will prompt if omitted.",
+ help="New password for user. Will prompt for a password if "
+ "this flag and `--password-file` are both omitted.",
+ )
+ password_group.add_argument(
+ "--password-file",
+ default=None,
+ help="File containing the new password for user. If set, will override `--password`.",
)
parser.add_argument(
"-t",
@@ -247,6 +254,11 @@ def main() -> None:
print(_NO_SHARED_SECRET_OPTS_ERROR, file=sys.stderr)
sys.exit(1)
+ if args.password_file:
+ password = _read_file(args.password_file, "password-file").strip()
+ else:
+ password = args.password
+
if args.server_url:
server_url = args.server_url
elif config is not None:
@@ -269,9 +281,7 @@ def main() -> None:
if args.admin or args.no_admin:
admin = args.admin
- register_new_user(
- args.user, args.password, server_url, secret, admin, args.user_type
- )
+ register_new_user(args.user, password, server_url, secret, admin, args.user_type)
def _read_file(file_path: Any, config_path: str) -> str:
--
2.44.1

View File

@ -0,0 +1,94 @@
From 1789416df425d22693b0055a6688d8686e0ee4a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io>
Date: Thu, 13 Jun 2024 14:38:19 +0200
Subject: [PATCH 2/2] register-new-matrix-user: add a flag to ignore already
existing users
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This allows to register users in a more declarative and stateless way.
Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
---
synapse/_scripts/register_new_matrix_user.py | 22 ++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/synapse/_scripts/register_new_matrix_user.py b/synapse/_scripts/register_new_matrix_user.py
index 972b35e2d..233e7267d 100644
--- a/synapse/_scripts/register_new_matrix_user.py
+++ b/synapse/_scripts/register_new_matrix_user.py
@@ -52,6 +52,7 @@ def request_registration(
user_type: Optional[str] = None,
_print: Callable[[str], None] = print,
exit: Callable[[int], None] = sys.exit,
+ exists_ok: bool = False,
) -> None:
url = "%s/_synapse/admin/v1/register" % (server_location.rstrip("/"),)
@@ -97,6 +98,10 @@ def request_registration(
r = requests.post(url, json=data)
if r.status_code != 200:
+ response = r.json()
+ if exists_ok and response["errcode"] == "M_USER_IN_USE":
+ _print("User already exists. Skipping.")
+ return
_print("ERROR! Received %d %s" % (r.status_code, r.reason))
if 400 <= r.status_code < 500:
try:
@@ -115,6 +120,7 @@ def register_new_user(
shared_secret: str,
admin: Optional[bool],
user_type: Optional[str],
+ exists_ok: bool = False,
) -> None:
if not user:
try:
@@ -154,7 +160,13 @@ def register_new_user(
admin = False
request_registration(
- user, password, server_location, shared_secret, bool(admin), user_type
+ user,
+ password,
+ server_location,
+ shared_secret,
+ bool(admin),
+ user_type,
+ exists_ok=exists_ok,
)
@@ -173,6 +185,11 @@ def main() -> None:
default=None,
help="Local part of the new user. Will prompt if omitted.",
)
+ parser.add_argument(
+ "--exists-ok",
+ action="store_true",
+ help="Do not fail if user already exists.",
+ )
password_group = parser.add_mutually_exclusive_group()
password_group.add_argument(
"-p",
@@ -192,6 +209,7 @@ def main() -> None:
default=None,
help="User type as specified in synapse.api.constants.UserTypes",
)
+
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
"-a",
@@ -281,7 +299,7 @@ def main() -> None:
if args.admin or args.no_admin:
admin = args.admin
- register_new_user(args.user, password, server_url, secret, admin, args.user_type)
+ register_new_user(args.user, password, server_url, secret, admin, args.user_type, exists_ok=args.exists_ok)
def _read_file(file_path: Any, config_path: str) -> str:
--
2.44.1

View File

@ -1,2 +1,3 @@
A federated messaging server with end-to-end encryption.
---
description = "A federated messaging server with end-to-end encryption."
---

View File

@ -6,17 +6,93 @@
}:
let
cfg = config.clan.matrix-synapse;
nginx-vhost = "matrix.${config.clan.matrix-synapse.domain}";
element-web =
pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; }
''
cp -r ${pkgs.element-web} $out
chmod -R u+w $out
jq '."default_server_config"."m.homeserver" = { "base_url": "https://${nginx-vhost}:443", "server_name": "${config.clan.matrix-synapse.domain}" }' \
> $out/config.json < ${pkgs.element-web}/config.json
ln -s $out/config.json $out/config.${nginx-vhost}.json
'';
# FIXME: This was taken from upstream. Drop this when our patch is upstream
synapseCfg = config.services.matrix-synapse;
wantedExtras =
synapseCfg.extras
++ lib.optional (synapseCfg.settings ? oidc_providers) "oidc"
++ lib.optional (synapseCfg.settings ? jwt_config) "jwt"
++ lib.optional (synapseCfg.settings ? saml2_config) "saml2"
++ lib.optional (synapseCfg.settings ? redis) "redis"
++ lib.optional (synapseCfg.settings ? sentry) "sentry"
++ lib.optional (synapseCfg.settings ? user_directory) "user-search"
++ lib.optional (synapseCfg.settings.url_preview_enabled) "url-preview"
++ lib.optional (synapseCfg.settings.database.name == "psycopg2") "postgres";
in
{
options.services.matrix-synapse.package = lib.mkOption { readOnly = false; };
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";
example = "example.com";
};
users = lib.mkOption {
default = { };
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
description = "The name of the user";
};
admin = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether the user should be an admin";
};
};
}
)
);
description = "A list of users. Not that only new users will be created and existing ones are not modified.";
example.alice = {
admin = true;
};
};
};
config = lib.mkIf cfg.enable {
imports = [
../postgresql
(lib.mkRemovedOptionModule [
"clan"
"matrix-synapse"
"enable"
] "Importing the module will already enable the service.")
../postgresql
];
config = {
services.matrix-synapse = {
package = lib.mkForce (
pkgs.matrix-synapse.override {
matrix-synapse-unwrapped = pkgs.matrix-synapse.unwrapped.overrideAttrs (_old: {
doInstallCheck = false; # too slow, nixpkgs maintainer already run this.
patches = [
# see: https://github.com/element-hq/synapse/pull/17304
./0001-register_new_matrix_user-add-password-file-flag.patch
./0002-register-new-matrix-user-add-a-flag-to-ignore-alread.patch
];
});
extras = wantedExtras;
plugins = synapseCfg.plugins;
}
);
enable = true;
settings = {
server_name = cfg.domain;
@ -29,6 +105,7 @@ in
"turn:turn.matrix.org?transport=udp"
"turn:turn.matrix.org?transport=tcp"
];
registration_shared_secret_path = "/run/synapse-registration-shared-secret";
listeners = [
{
port = 8008;
@ -49,45 +126,76 @@ in
}
];
};
extraConfigFiles = [ "/var/lib/matrix-synapse/registration_shared_secret.yaml" ];
};
systemd.services.matrix-synapse.serviceConfig.ExecStartPre = [
"+${pkgs.writeScript "copy_registration_shared_secret" ''
#!/bin/sh
cp ${config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path} /var/lib/matrix-synapse/registration_shared_secret.yaml
chown matrix-synapse:matrix-synapse /var/lib/matrix-synapse/registration_shared_secret.yaml
chmod 600 /var/lib/matrix-synapse/registration_shared_secret.yaml
''}"
];
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;
systemd.tmpfiles.settings."01-matrix" = {
"/run/synapse-registration-shared-secret" = {
C.argument =
config.clan.core.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path;
z = {
mode = "0400";
user = "matrix-synapse";
};
};
};
clan.postgresql.users.matrix-synapse = { };
clan.postgresql.databases.matrix-synapse.create.options = {
TEMPLATE = "template0";
LC_COLLATE = "C";
LC_CTYPE = "C";
ENCODING = "UTF8";
OWNER = "matrix-synapse";
};
clan.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ];
clan.core.facts.services =
{
"matrix-synapse" = {
secret."synapse-registration_shared_secret" = { };
generator.path = with pkgs; [
coreutils
pwgen
];
generator.script = ''
echo -n "$(pwgen -s 32 1)" > "$secrets"/synapse-registration_shared_secret
'';
};
}
// lib.mapAttrs' (
name: user:
lib.nameValuePair "matrix-password-${user.name}" {
secret."matrix-password-${user.name}" = { };
generator.path = with pkgs; [ xkcdpass ];
generator.script = ''
xkcdpass -n 4 -d - > "$secrets"/${lib.escapeShellArg "matrix-password-${user.name}"}
'';
}
];
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE DATABASE "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
};
) cfg.users;
systemd.services.matrix-synapse =
let
usersScript =
''
while ! ${pkgs.netcat}/bin/nc -z -v ::1 8008; do
if ! kill -0 "$MAINPID"; then exit 1; fi
sleep 1;
done
''
+ lib.concatMapStringsSep "\n" (user: ''
# only create user if it doesn't exist
/run/current-system/sw/bin/matrix-synapse-register_new_matrix_user --exists-ok --password-file ${
config.clan.core.facts.services."matrix-password-${user.name}".secret."matrix-password-${user.name}".path
} --user "${user.name}" ${if user.admin then "--admin" else "--no-admin"}
'') (lib.attrValues cfg.users);
in
{
path = [ pkgs.curl ];
serviceConfig.ExecStartPost = [
(''+${pkgs.writeShellScript "matrix-synapse-create-users" usersScript}'')
];
};
services.nginx = {
enable = true;
virtualHosts = {
@ -102,7 +210,7 @@ in
return 200 '${
builtins.toJSON {
"m.homeserver" = {
"base_url" = "https://matrix.${cfg.domain}";
"base_url" = "https://${nginx-vhost}";
};
"m.identity_server" = {
"base_url" = "https://vector.im";
@ -111,15 +219,12 @@ in
}';
'';
};
"matrix.${cfg.domain}" = {
${nginx-vhost} = {
forceSSL = true;
enableACME = true;
locations."/_matrix" = {
proxyPass = "http://localhost:8008";
};
locations."/test".extraConfig = ''
return 200 "Hello, world!";
'';
locations."/_matrix".proxyPass = "http://localhost:8008";
locations."/_synapse".proxyPass = "http://localhost:8008";
locations."/".root = element-web;
};
};
};

View File

@ -1,2 +1,3 @@
A desktop streaming client optimized for remote gaming and synchronized movie viewing.
---
description = "A desktop streaming client optimized for remote gaming and synchronized movie viewing."
---

View File

@ -13,10 +13,10 @@ in
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 ""
config.clan.core.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 ""
config.clan.core.facts.services.moonlight.secret."moonlight.key".path or ""
}"
];
@ -45,7 +45,7 @@ in
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 ""
config.clan.core.facts.services.moonlight.public."moonlight.cert".value or ""
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
serviceConfig = {
Type = "oneshot";
@ -68,7 +68,7 @@ in
};
};
clanCore.facts.services.moonlight = {
clan.core.facts.services.moonlight = {
secret."moonlight.key" = { };
secret."moonlight.cert" = { };
public."moonlight.cert" = { };

View File

@ -0,0 +1,3 @@
---
description = "A free and open-source relational database management system (RDBMS) emphasizing extensibility and SQL compliance."
---

View File

@ -0,0 +1,222 @@
{
pkgs,
lib,
config,
...
}:
let
createDatatbaseState =
db:
let
folder = "/var/backup/postgres/${db.name}";
current = "${folder}/pg-dump";
compression = lib.optionalString (lib.versionAtLeast config.services.postgresql.package.version "16") "--compress=zstd";
in
{
folders = [ folder ];
preBackupScript = ''
export PATH=${
lib.makeBinPath [
config.services.postgresql.package
config.systemd.package
pkgs.coreutils
pkgs.util-linux
pkgs.zstd
]
}
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
sleep 1
done
mkdir -p "${folder}"
runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp"
mv "${current}.tmp" ${current}
'';
postRestoreScript = ''
export PATH=${
lib.makeBinPath [
config.services.postgresql.package
config.systemd.package
pkgs.coreutils
pkgs.util-linux
pkgs.zstd
pkgs.gnugrep
]
}
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
sleep 1
done
echo "Waiting for postgres to be ready..."
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
if ! systemctl is-active postgresql; then exit 1; fi
sleep 0.1
done
if [[ -e "${current}" ]]; then
(
systemctl stop ${lib.concatStringsSep " " db.restore.stopOnRestore}
trap "systemctl start ${lib.concatStringsSep " " db.restore.stopOnRestore}" EXIT
mkdir -p "${folder}"
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
runuser -u postgres -- dropdb "${db.name}"
fi
runuser -u postgres -- pg_restore -C -d postgres "${current}"
)
else
echo No database backup found, skipping restore
fi
'';
};
createDatabase = db: ''
CREATE DATABASE "${db.name}" ${
lib.concatStringsSep " " (
lib.mapAttrsToList (name: value: "${name} = '${value}'") db.create.options
)
}
'';
cfg = config.clan.postgresql;
userClauses = lib.mapAttrsToList (
_: user:
''$PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' ''
) cfg.users;
databaseClauses = lib.mapAttrsToList (
name: db:
lib.optionalString db.create.enable ''$PSQL -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${name}'" | grep -q 1 || $PSQL -d postgres -c ${lib.escapeShellArg (createDatabase db)} ''
) cfg.databases;
in
{
options.clan.postgresql = {
# we are reimplemeting ensureDatabase and ensureUser options here to allow to create databases with options
databases = lib.mkOption {
default = { };
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
description = "Database name.";
};
service = lib.mkOption {
type = lib.types.str;
default = name;
description = "Service name that we associate with the database.";
};
# set to false, in case the upstream module uses ensureDatabase option
create.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Create the database if it does not exist.";
};
create.options = lib.mkOption {
type = lib.types.lazyAttrsOf lib.types.str;
default = { };
example = {
TEMPLATE = "template0";
LC_COLLATE = "C";
LC_CTYPE = "C";
ENCODING = "UTF8";
OWNER = "foo";
};
};
restore.stopOnRestore = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "List of systemd services to stop before restoring the database.";
};
};
}
)
);
};
users = lib.mkOption {
default = { };
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options.name = lib.mkOption {
type = lib.types.str;
default = name;
};
}
)
);
};
};
config = {
services.postgresql.settings = {
wal_level = "replica";
max_wal_senders = 3;
};
services.postgresql.enable = true;
# We are duplicating a bit the upstream module but allow to create databases with options
systemd.services.postgresql.postStart = ''
PSQL="psql --port=${builtins.toString config.services.postgresql.settings.port}"
while ! $PSQL -d postgres -c "" 2> /dev/null; do
if ! kill -0 "$MAINPID"; then exit 1; fi
sleep 0.1
done
${lib.concatStringsSep "\n" userClauses}
${lib.concatStringsSep "\n" databaseClauses}
'';
clan.core.state = lib.mapAttrs' (
_: db: lib.nameValuePair db.service (createDatatbaseState db)
) config.clan.postgresql.databases;
environment.systemPackages = builtins.map (
db:
let
folder = "/var/backup/postgres/${db.name}";
current = "${folder}/pg-dump";
in
pkgs.writeShellScriptBin "postgres-db-restore-command-${db.name}" ''
export PATH=${
lib.makeBinPath [
config.services.postgresql.package
config.systemd.package
pkgs.coreutils
pkgs.util-linux
pkgs.zstd
pkgs.gnugrep
]
}
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
sleep 1
done
echo "Waiting for postgres to be ready..."
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
if ! systemctl is-active postgresql; then exit 1; fi
sleep 0.1
done
if [[ -e "${current}" ]]; then
(
${
lib.optionalString (db.restore.stopOnRestore != [ ]) ''
systemctl stop ${builtins.toString db.restore.stopOnRestore}
trap "systemctl start ${builtins.toString db.restore.stopOnRestore}" EXIT
''
}
mkdir -p "${folder}"
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
runuser -u postgres -- dropdb "${db.name}"
fi
runuser -u postgres -- pg_restore -C -d postgres "${current}"
)
else
echo No database backup found, skipping restore
fi
''
) (builtins.attrValues config.clan.postgresql.databases);
};
}

View File

@ -1,4 +1,5 @@
Automatically generates and configures a password for the root user.
---
description = "Automatically generates and configures a password for the root user."
---
After the system was installed/deployed the following command can be used to display the root-password:

View File

@ -2,9 +2,9 @@
{
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 = {
config.clan.core.facts.services.root-password.secret.password-hash.path;
sops.secrets."${config.clan.core.machineName}-password-hash".neededForUsers = true;
clan.core.facts.services.root-password = {
secret.password = { };
secret.password-hash = { };
generator.path = with pkgs; [
@ -13,8 +13,8 @@
mkpasswd
];
generator.script = ''
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/password
cat $secrets/password | mkpasswd -s -m sha-512 > $secrets/password-hash
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > $secrets/password
cat $secrets/password | mkpasswd -s -m sha-512 | tr -d "\n" > $secrets/password-hash
'';
};
}

View File

@ -1,2 +1,3 @@
Enables secure remote access to the machine over ssh
---
description = "Enables secure remote access to the machine over ssh"
---

View File

@ -2,15 +2,19 @@
{
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
# We might want to remove this once, openssh is fixed everywhere:
# Workaround for CVE-2024-6387
# https://github.com/NixOS/nixpkgs/pull/323753#issuecomment-2199762128
services.openssh.settings.LoginGraceTime = 0;
services.openssh.hostKeys = [
{
path = config.clanCore.facts.services.openssh.secret."ssh.id_ed25519".path;
path = config.clan.core.facts.services.openssh.secret."ssh.id_ed25519".path;
type = "ed25519";
}
];
clanCore.facts.services.openssh = {
clan.core.facts.services.openssh = {
secret."ssh.id_ed25519" = { };
public."ssh.id_ed25519.pub" = { };
generator.path = [

View File

@ -1,2 +1,3 @@
Statically configure the host names of machines based on their respective zerotier-ip.
---
description = "Statically configure the host names of machines based on their respective zerotier-ip."
---

View File

@ -4,7 +4,7 @@
excludeHosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default =
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clanCore.machineName ];
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clan.core.machineName ];
description = "Hosts that should be excluded";
};
topLevelDomain = lib.mkOption {
@ -16,13 +16,23 @@
config.networking.hosts =
let
clanDir = config.clanCore.clanDir;
clanDir = config.clan.core.clanDir;
machineDir = clanDir + "/machines/";
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
machines = builtins.readDir machineDir;
machinesFileSet = builtins.readDir machineDir;
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
networkIpsUnchecked = builtins.map (
machine:
let
fullPath = zerotierIpMachinePath machine;
in
if builtins.pathExists fullPath then machine else null
) machines;
networkIps = lib.filter (machine: machine != null) networkIpsUnchecked;
machinesWithIp = lib.filterAttrs (name: _: (lib.elem name networkIps)) machinesFileSet;
filteredMachines = lib.filterAttrs (
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
) machines;
) machinesWithIp;
in
lib.filterAttrs (_: value: value != null) (
lib.mapAttrs' (
@ -38,7 +48,7 @@
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
)
else
null
{ }
) filteredMachines
);
}

View File

@ -1,2 +1,3 @@
A desktop streaming server optimized for remote gaming and synchronized movie viewing.
---
description = "A desktop streaming server optimized for remote gaming and synchronized movie viewing."
---

View File

@ -97,10 +97,10 @@ in
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 ""
config.clan.core.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 ""
config.clan.core.facts.services.sunshine.secret."sunshine.key".path or ""
}"
];
@ -117,8 +117,8 @@ in
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 "")
(config.clan.core.facts.services.sunshine.secret."sunshine.key".path or "")
(config.clan.core.facts.services.sunshine.secret."sunshine.cert".path or "")
];
};
wantedBy = [ "graphical-session.target" ];
@ -137,7 +137,7 @@ in
startLimitIntervalSec = 500;
script = ''
${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
config.clan.core.facts.services.sunshine.public.sunshine-uuid.value or null
} --state-file /var/lib/sunshine/state.json
'';
serviceConfig = {
@ -173,9 +173,9 @@ in
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
config.clan.core.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
config.clan.core.facts.services.sunshine.public."sunshine.cert".value or null
}'
'';
serviceConfig = {
@ -187,7 +187,7 @@ in
wantedBy = [ "graphical-session.target" ];
};
clanCore.facts.services.ergochat = {
clan.core.facts.services.ergochat = {
secret."sunshine.key" = { };
secret."sunshine.cert" = { };
public."sunshine-uuid" = { };

View File

@ -0,0 +1,3 @@
---
description = "Statically configure syncthing peers through clan"
---

View File

@ -0,0 +1,108 @@
{
lib,
config,
pkgs,
...
}:
let
clanDir = config.clan.core.clanDir;
machineDir = clanDir + "/machines/";
syncthingPublicKeyPath = machines: machineDir + machines + "/facts/syncthing.pub";
machinesFileSet = builtins.readDir machineDir;
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
syncthingPublicKeysUnchecked = builtins.map (
machine:
let
fullPath = syncthingPublicKeyPath machine;
in
if builtins.pathExists fullPath then machine else null
) machines;
syncthingPublicKeyMachines = lib.filter (machine: machine != null) syncthingPublicKeysUnchecked;
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
networkIpsUnchecked = builtins.map (
machine:
let
fullPath = zerotierIpMachinePath machine;
in
if builtins.pathExists fullPath then machine else null
) machines;
networkIpMachines = lib.filter (machine: machine != null) networkIpsUnchecked;
devices = builtins.map (machine: {
name = machine;
value = {
name = machine;
id = (lib.removeSuffix "\n" (builtins.readFile (syncthingPublicKeyPath machine)));
addresses =
[ "dynamic" ]
++ (
if (lib.elem machine networkIpMachines) then
[ "tcp://[${(lib.removeSuffix "\n" (builtins.readFile (zerotierIpMachinePath machine)))}]:22000" ]
else
[ ]
);
};
}) syncthingPublicKeyMachines;
in
{
options.clan.syncthing-static-peers = {
excludeMachines = lib.mkOption {
type = lib.types.listOf lib.types.str;
example = [ config.clan.core.machineName ];
default = [ ];
description = ''
Machines that should not be added.
'';
};
};
config.services.syncthing.settings.devices = (builtins.listToAttrs devices);
imports = [
{
# Syncthing ports: 8384 for remote access to GUI
# 22000 TCP and/or UDP for sync traffic
# 21027/UDP for discovery
# source: https://docs.syncthing.net/users/firewall.html
networking.firewall.interfaces."zt+".allowedTCPPorts = [
8384
22000
];
networking.firewall.allowedTCPPorts = [ 8384 ];
networking.firewall.interfaces."zt+".allowedUDPPorts = [
22000
21027
];
# Activates inotify compatibility on syncthing
# use mkOverride 900 here as it otherwise would collide with the default of the
# upstream nixos xserver.nix
boot.kernel.sysctl."fs.inotify.max_user_watches" = lib.mkOverride 900 524288;
services.syncthing = {
enable = true;
configDir = "/var/lib/syncthing";
group = "syncthing";
key = lib.mkDefault config.clan.core.facts.services.syncthing.secret."syncthing.key".path or null;
cert = lib.mkDefault config.clan.core.facts.services.syncthing.secret."syncthing.cert".path or null;
};
clan.core.facts.services.syncthing = {
secret."syncthing.key" = { };
secret."syncthing.cert" = { };
public."syncthing.pub" = { };
generator.path = [
pkgs.coreutils
pkgs.gnugrep
pkgs.syncthing
];
generator.script = ''
syncthing generate --config "$secrets"
mv "$secrets"/key.pem "$secrets"/syncthing.key
mv "$secrets"/cert.pem "$secrets"/syncthing.cert
cat "$secrets"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$facts"/syncthing.pub
'';
};
}
];
}

View File

@ -1,4 +1,5 @@
A secure, file synchronization app for devices over networks, offering a private alternative to cloud services.
---
description = "A secure, file synchronization app for devices over networks, offering a private alternative to cloud services."
---
## Usage

View File

@ -9,8 +9,8 @@
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.clan.core.facts.services.syncthing.public."syncthing.pub".value or null;
defaultText = "config.clan.core.facts.services.syncthing.public.\"syncthing.pub\".value";
};
introducer = lib.mkOption {
description = ''
@ -119,7 +119,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.clan.core.facts.services.syncthing.secret."syncthing.api".path or null;
in
lib.mkIf config.clan.syncthing.autoAcceptDevices {
description = "Syncthing auto accept devices";
@ -161,7 +161,7 @@
systemd.services.syncthing-init-api-key =
let
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
apiKey = config.clan.core.facts.services.syncthing.secret."syncthing.api".path or null;
in
lib.mkIf config.clan.syncthing.autoAcceptDevices {
description = "Set the api key";
@ -183,7 +183,7 @@
};
};
clanCore.facts.services.syncthing = {
clan.core.facts.services.syncthing = {
secret."syncthing.key" = { };
secret."syncthing.cert" = { };
secret."syncthing.api" = { };

View File

@ -1,2 +1,3 @@
Modern web IRC client
---
description = "Modern web IRC client"
---

View File

@ -11,5 +11,5 @@ _: {
};
};
clanCore.state.thelounde.folders = [ "/var/lib/thelounge" ];
clan.core.state.thelounde.folders = [ "/var/lib/thelounge" ];
}

View File

@ -1,2 +1,3 @@
This module sets the `clan.lol` and `nix-community` cache up as a trusted cache.
---
description = "This module sets the `clan.lol` and `nix-community` cache up as a trusted cache."
----

View File

@ -1,4 +1,5 @@
Automatically generates and configures a password for the specified user account.
---
description = "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.

View File

@ -22,9 +22,9 @@
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 = {
config.clan.core.facts.services.user-password.secret.user-password-hash.path;
sops.secrets."${config.clan.core.machineName}-user-password-hash".neededForUsers = true;
clan.core.facts.services.user-password = {
secret.user-password = { };
secret.user-password-hash = { };
generator.prompt = (
@ -37,12 +37,12 @@
mkpasswd
];
generator.script = ''
if [[ -n $prompt_value ]]; then
echo $prompt_value > $secrets/user-password
if [[ -n ''${prompt_value-} ]]; then
echo $prompt_value | tr -d "\n" > $secrets/user-password
else
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/user-password
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > $secrets/user-password
fi
cat $secrets/user-password | mkpasswd -s -m sha-512 > $secrets/user-password-hash
cat $secrets/user-password | mkpasswd -s -m sha-512 | tr -d "\n" > $secrets/user-password-hash
'';
};
};

View File

@ -1,2 +1,3 @@
A lightweight desktop manager
---
description = "A lightweight desktop manager"
---

View File

@ -1,4 +1,5 @@
Statically configure the `zerotier` peers of a clan network.
---
description = "Statically configure the `zerotier` peers of a clan network."
---
Statically configure the `zerotier` peers of a clan network.

View File

@ -2,11 +2,10 @@
lib,
config,
pkgs,
inputs,
...
}:
let
clanDir = config.clanCore.clanDir;
clanDir = config.clan.core.clanDir;
machineDir = clanDir + "/machines/";
machinesFileSet = builtins.readDir machineDir;
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
@ -20,7 +19,7 @@ let
if builtins.pathExists fullPath then builtins.readFile fullPath else null
) machines;
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
networkId = builtins.elemAt networkIds 0;
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
@ -28,40 +27,47 @@ in
options.clan.zerotier-static-peers = {
excludeHosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ config.clanCore.machineName ];
default = [ config.clan.core.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.static-hosts.excludeHosts)
networkIpsUnchecked = builtins.map (
machine:
let
fullPath = zerotierIpMachinePath machine;
in
if builtins.pathExists fullPath then machine else null
) machines;
networkIps = lib.filter (machine: machine != null) networkIpsUnchecked;
machinesWithIp = lib.filterAttrs (name: _: (lib.elem name networkIps)) machinesFileSet;
filteredMachines = lib.filterAttrs (
name: _: !(lib.elem name config.clan.zerotier-static-peers.excludeHosts)
) machinesWithIp;
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
lib.nameValuePair (builtins.readFile fullPath) [ machine ]
) filteredMachines
);
in
lib.mkIf (config.clan.networking.zerotier.controller.enable) {
wantedBy = [ "multi-user.target" ];
after = [ "zerotierone.service" ];
path = [ pkgs.zerotierone ];
path = [
config.clan.core.clanPkgs.zerotierone
];
serviceConfig.ExecStart = pkgs.writeScript "static-zerotier-peers-autoaccept" ''
#!/bin/sh
${lib.concatMapStringsSep "\n" (host: ''
${
inputs.clan-core.packages.${pkgs.system}.zerotier-members
${
config.clan.core.clanPkgs.zerotier-members
}/bin/zerotier-members allow --member-ip ${host}
'') hosts}
'';

View File

@ -1,2 +1,3 @@
Enable ZeroTier VPN over TCP for networks where UDP is blocked.
---
description = "Enable ZeroTier VPN over TCP for networks where UDP is blocked."
---

View File

@ -26,6 +26,7 @@
devShells.default = pkgs.mkShell {
packages = [
select-shell
pkgs.nix-unit
pkgs.tea
# Better error messages than nix 2.18
pkgs.nixVersions.latest

View File

@ -44,6 +44,14 @@ Let's get your development environment up and running:
```bash
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
```
5. **Create an access token**:
- Log in to Gitea.
- Go to your account settings.
- Navigate to the Applications section.
- Click Generate New Token.
- Name your token and select all available scopes.
- Generate the token and copy it for later use.
- Your access token is now ready to use with all permissions.
5. **Register Your Gitea Account Locally**:
@ -54,9 +62,8 @@ Let's get your development environment up and running:
- 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
- Do you have an access token? Yes
- Token: <yourtoken>
- Set Optional settings: No
@ -121,7 +128,7 @@ git+file:///home/lhebendanz/Projects/clan-core
│ ├───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)
│ ├───clan-app 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)

View File

@ -49,21 +49,23 @@ 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
- 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/postgresql.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/sunshine.md
- reference/clanModules/syncthing-static-peers.md
- reference/clanModules/syncthing.md
- reference/clanModules/thelounge.md
- reference/clanModules/trusted-nix-caches.md
- reference/clanModules/user-password.md
@ -71,16 +73,18 @@ nav:
- 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/index.md
- reference/cli/machines.md
- reference/cli/secrets.md
- reference/cli/show.md
- reference/cli/ssh.md
- reference/cli/state.md
- reference/cli/vms.md
- Clan Core:
- reference/clan-core/index.md

View File

@ -12,13 +12,14 @@
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
jsonDocs = import ./get-module-docs.nix {
inherit (inputs) nixpkgs;
inherit pkgs self;
inherit pkgs;
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);
# clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes);
# clanModulesMeta = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesMeta);
# Simply evaluated options (JSON)
renderOptions =
@ -29,6 +30,7 @@
nativeBuildInputs = [
pkgs.python3
pkgs.mypy
self'.packages.clan-cli
];
}
''
@ -49,17 +51,25 @@
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}
module-docs =
pkgs.runCommand "rendered"
{
nativeBuildInputs = [
pkgs.python3
self'.packages.clan-cli
];
}
''
export CLAN_CORE_PATH=${self}
export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json
# A file that contains the links to all clanModule docs
export CLAN_MODULES=${clanModulesFileInfo}
mkdir $out
mkdir $out
# The python script will place mkDocs files in the output directory
python3 ${renderOptions}
'';
# The python script will place mkDocs files in the output directory
python3 ${renderOptions}
'';
in
{
devShells.docs = pkgs.callPackage ./shell.nix {

View File

@ -3,7 +3,6 @@
pkgs,
clanCore,
clanModules,
self,
}:
let
allNixosModules = (import "${nixpkgs}/nixos/modules/module-list.nix") ++ [
@ -13,7 +12,7 @@ let
clanCoreNixosModules = [
clanCore
{ clanCore.clanDir = ./.; }
{ clan.core.clanDir = ./.; }
] ++ allNixosModules;
# TODO: optimally we would not have to evaluate all nixos modules for every page
@ -25,6 +24,8 @@ let
# improves eval performance slightly (10%)
getOptions = modules: (clanCoreNixos.extendModules { inherit modules; }).options;
getOptionsWithoutCore = modules: builtins.removeAttrs (getOptions modules) [ "core" ];
evalDocs =
options:
pkgs.nixosOptionsDoc {
@ -34,18 +35,13 @@ let
# 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
name: module: (evalDocs ((getOptionsWithoutCore [ module ]).clan.${name} or { })).optionsJSON
) clanModules;
# clanCore docs
clanCoreDocs = (evalDocs (getOptions [ ]).clanCore).optionsJSON;
clanCoreDocs = (evalDocs (getOptions [ ]).clan.core).optionsJSON;
in
{
inherit clanModulesReadmes;
clanCore = clanCoreDocs;
clanModules = clanModulesDocs;
}

View File

@ -28,10 +28,12 @@ import os
from pathlib import Path
from typing import Any
from clan_cli.api.modules import Frontmatter, extract_frontmatter, get_roles
# Get environment variables
CLAN_CORE = os.getenv("CLAN_CORE")
CLAN_CORE_PATH = os.getenv("CLAN_CORE_PATH")
CLAN_CORE_DOCS = os.getenv("CLAN_CORE_DOCS")
CLAN_MODULES = os.environ.get("CLAN_MODULES")
CLAN_MODULES_READMES = os.environ.get("CLAN_MODULES_READMES")
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"]}`
@ -120,7 +124,7 @@ def render_option(name: str, option: dict[str, Any], level: int = 3) -> str:
def module_header(module_name: str) -> str:
return f"# {module_name}\n"
return f"# {module_name}\n\n"
def module_usage(module_name: str) -> str:
@ -137,7 +141,7 @@ To use this module, import it like this:
"""
clan_core_descr = """ClanCore delivers all the essential features for every clan.
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.
"""
@ -146,9 +150,9 @@ options_head = "\n## Module Options\n"
def produce_clan_core_docs() -> None:
if not CLAN_CORE:
if not CLAN_CORE_DOCS:
raise ValueError(
f"Environment variables are not set correctly: $CLAN_CORE={CLAN_CORE}"
f"Environment variables are not set correctly: $CLAN_CORE_DOCS={CLAN_CORE_DOCS}"
)
if not OUT:
@ -156,14 +160,14 @@ def produce_clan_core_docs() -> None:
# A mapping of output file to content
core_outputs: dict[str, str] = {}
with open(CLAN_CORE) as f:
with open(CLAN_CORE_DOCS) 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:
if len(option_name.split(".")) <= 3:
# i.e. clan-core.clanDir
output = core_outputs.get(
outfile,
@ -174,7 +178,7 @@ def produce_clan_core_docs() -> None:
core_outputs[outfile] = output
else:
# Clan sub-options
[_, sub] = option_name.split(".")[0:2]
[_, sub] = option_name.split(".")[1:3]
outfile = f"{module_name}/{sub}.md"
# Get the content or write the header
output = core_outputs.get(outfile, render_option_header(sub))
@ -188,14 +192,42 @@ def produce_clan_core_docs() -> None:
of.write(output)
def render_roles(roles: list[str] | None, module_name: str) -> str:
if roles:
roles_list = "\n".join([f" - `{r}`" for r in roles])
return f"""
???+ tip "Inventory usage"
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(
f"Environment variables are not set correctly: $CLAN_MODULES={CLAN_MODULES}"
)
if not CLAN_MODULES_READMES:
if not CLAN_CORE_PATH:
raise ValueError(
f"Environment variables are not set correctly: $CLAN_MODULES_READMES={CLAN_MODULES_READMES}"
f"Environment variables are not set correctly: $CLAN_CORE_PATH={CLAN_CORE_PATH}"
)
if not OUT:
@ -204,18 +236,36 @@ def produce_clan_modules_docs() -> None:
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)
# 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():
readme_file = Path(CLAN_CORE_PATH) / "clanModules" / module_name / "README.md"
print(module_name, readme_file)
with open(readme_file) as f:
readme = f.read()
frontmatter: Frontmatter
frontmatter, readme_content = extract_frontmatter(readme, str(readme_file))
print(frontmatter, readme_content)
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"
if frontmatter.description:
output += f"**{frontmatter.description}**\n\n"
output += f"{readme_content}\n"
# get_roles(str) -> list[str] | None
roles = get_roles(str(Path(CLAN_CORE_PATH) / "clanModules" / module_name))
if roles:
output += render_roles(roles, module_name)
output += module_usage(module_name)

View File

@ -0,0 +1,132 @@
---
title: "Dev Report: Declarative Backups and Restore"
description: "An extension to the NixOS module system to declaratively describe how application state is backed up and restored."
authors:
- Mic92
date: 2024-06-24
slug: backups
---
Our goal with [Clan](https://clan.lol/) is to give users control over their data.
However, with great power comes great responsibility, and owning your data means you also need to take care of backups yourself.
In our experience, setting up automatic backups is often a tedious process as it requires custom integration of the backup software and
the services that produce the state. More important than the backup is the restore.
Restores are often not well tested or documented, and if not working correctly, they can render the backup useless.
In Clan, we want to make backup and restore a first-class citizen.
Every service should describe what state it produces and how it can be backed up and restored.
In this article, we will discuss how our backup interface in Clan works.
The interface allows different backup software to be used interchangeably and
allows module authors to define custom backup and restore logic for their services.
## First Comes the State
Our services are built from Clan modules. Clan modules are essentially [NixOS modules](https://wiki.nixos.org/wiki/NixOS_modules), the basic configuration components of NixOS.
However, we have enhanced them with additional features provided by Clan and restricted certain option types to enable configuration through a [graphical interface](https://docs.clan.lol/blog/2024/05/25/jsonschema-converter/).
In a simple case, this can be just a bunch of directories, such as what we define for our [ZeroTier](https://www.zerotier.com/) VPN service.
```nix
{
clan.core.state.zerotier.folders = [ "/var/lib/zerotier-one" ];
}
```
For other systems, we need more complex backup and restore logic.
For each state, we can provide custom command hooks for backing up and restoring.
In our PostgreSQL module, for example, we define `preBackupCommand` and `postRestoreCommand` to use `pg_dump` and `pg_restore` to backup and restore individual databases:
```nix
preBackupCommand = ''
# ...
runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp"
# ...
'';
postRestoreCommand = ''
# ...
runuser -u postgres -- dropdb "${db.name}"
runuser -u postgres -- pg_restore -C -d postgres "${current}"
# ...
'';
```
## Then the Backup
Our CLI unifies the different backup providers in one [interface](https://docs.clan.lol/reference/cli/backups/).
As of now, we support backups using [BorgBackup](https://www.borgbackup.org/) and
a backup module called "localbackup" based on [rsnapshot](https://rsnapshot.org/), optimized for backup on locally attached storage media.
To use different backup software, a module needs to set the options provided by our backup interface.
The following Nix code is a toy example that uses the `tar` program to perform backup and restore to illustrate how the backup interface works:
```nix
clan.core.backups.providers.tar = {
list = ''
echo /var/lib/system-back.tar
'';
create = let
uniqueFolders = lib.unique (
lib.flatten (lib.mapAttrsToList (_name: state: state.folders) config.clan.core.state)
);
in ''
# FIXME: a proper implementation should also run `state.preBackupCommand` of each state
if [ -f /var/lib/system-back.tar ]; then
tar -uvpf /var/lib/system-back.tar ${builtins.toString uniqueFolders}
else
tar -cvpf /var/lib/system-back.tar ${builtins.toString uniqueFolders}
fi
'';
restore = ''
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
echo "${FOLDER[@]}" > /run/folders-to-restore.txt
tar -xvpf /var/lib/system-back.tar -C / -T /run/folders-to-restore.txt
'';
};
```
For better real-world implementations, check out the implementations for [BorgBackup](https://git.clan.lol/clan/clan-core/src/branch/main/clanModules/borgbackup/default.nix)
and [localbackup](https://git.clan.lol/clan/clan-core/src/branch/main/clanModules/localbackup/default.nix).
## What It Looks Like to the End User
After following the guide for configuring a [backup](https://docs.clan.lol/getting-started/backups/),
users can use the CLI to create backups, list them, and restore them.
Backups can be created through the CLI like this:
```
clan backups create web01
```
BorgBackup will also create backups itself every day by default.
Completed backups can be listed like this:
```
clan backups list web01
...
web01::u366395@u366395.your-storagebox.de:/./borgbackup::web01-web01-2024-06-18T01:00:00
web03::u366395@u366395.your-storagebox.de:/./borgbackup::web01-web01-2024-06-19T01:00:00
```
One cool feature of our backup system is that it is aware of individual services/applications.
Let's say we want to restore the state of our [Matrix](https://matrix.org/) chat server; we can just specify it like this:
```
clan backups restore --service matrix-synapse web01 borgbackup web03::u366395@u366395.your-storagebox.de:/./borgbackup::web01-web01-2024-06-19T01:00:00
```
In this case, it will first stop the matrix-synapse systemd service, then delete the [PostgreSQL](https://www.postgresql.org/) database, restore the database from the backup, and then start the matrix-synapse service again.
## Future work
As of now we implemented our backup and restore for a handful of services and we expect to refine the interface as we test the interface for more applications.
Currently, our backup implementation backs up filesystem state from running services.
This can lead to inconsistencies if applications change the state while the backup is running.
In the future, we hope to make backups more atomic by backing up a filesystem snapshot instead of normal directories.
This, however, requires the use of modern filesystems that support these features.

View File

@ -61,7 +61,7 @@ Clan is for anyone and everyone who believes in the power of open source technol
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).
Connect with us on our [Matrix channel at clan.lol](https://matrix.to/#/#clan:clan.lol) 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).

View File

@ -190,5 +190,5 @@ We hope these experiments inspire the community, encourage contributions and fur
- [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)
- [Our Matrix Channel](https://matrix.to/#/#clan:clan.lol)
- [react-jsonschema-form Playground](https://rjsf-team.github.io/react-jsonschema-form/)

View File

@ -10,4 +10,4 @@ Last week, we added a new documentation hub for clan at [docs.clan.lol](https://
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).
Otherwise drop by in our [matrix channel](https://matrix.to/#/#clan:clan.lol).

View File

@ -98,7 +98,7 @@ Start by indicating where your backup data should be sent. Replace `hostname` wi
Decide which folders you want to back up. For example, to backup your home and root directories:
```nix
{ clanCore.state.userdata.folders = [ "/home" "/root" ]; }
{ clan.core.state.userdata.folders = [ "/home" "/root" ]; }
```
3. **Generate Backup Credentials:**
@ -116,7 +116,7 @@ On the server where backups will be stored, enable the SSH daemon and set up a r
services.borgbackup.repos.myhostname = {
path = "/var/lib/borgbackup/myhostname";
authorizedKeys = [
(builtins.readFile ./machines/myhostname/facts/borgbackup.ssh.pub)
(builtins.readFile (config.clan.core.clanDir + "/machines/myhostname/facts/borgbackup.ssh.pub"))
];
};
}

View File

@ -4,14 +4,14 @@
In the `flake.nix` file:
- [x] set a unique `clanName`.
- [x] set a unique `name`.
=== "**buildClan**"
```nix title="clan-core.lib.buildClan"
buildClan {
# Set a unique name
clanName = "Lobsters";
# Set a unique name
meta.name = "Lobsters";
# Should usually point to the directory of flake.nix
directory = ./.;
@ -30,8 +30,8 @@ In the `flake.nix` file:
```nix title="clan-core.flakeModules.default"
clan = {
# Set a unique name
clanName = "Lobsters";
# Set a unique name
meta.name = "Lobsters";
machines = {
jon = {
@ -61,13 +61,13 @@ Adding or configuring a new machine requires two simple steps:
```{.shellSession hl_lines="6" .no-copy}
NAME ID-LINK FSTYPE SIZE MOUNTPOINT
sda usb-ST_16GB_AA6271026J1000000509-0:0 14.9G
├─sda1 usb-ST_16GB_AA6271026J1000000509-0:0-part1 1M
sda usb-ST_16GB_AA6271026J1000000509-0:0 14.9G
├─sda1 usb-ST_16GB_AA6271026J1000000509-0:0-part1 1M
├─sda2 usb-ST_16GB_AA6271026J1000000509-0:0-part2 vfat 100M /boot
└─sda3 usb-ST_16GB_AA6271026J1000000509-0:0-part3 ext4 2.9G /
nvme0n1 nvme-eui.e8238fa6bf530001001b448b4aec2929 476.9G
├─nvme0n1p1 nvme-eui.e8238fa6bf530001001b448b4aec2929-part1 vfat 512M
├─nvme0n1p2 nvme-eui.e8238fa6bf530001001b448b4aec2929-part2 ext4 459.6G
nvme0n1 nvme-eui.e8238fa6bf530001001b448b4aec2929 476.9G
├─nvme0n1p1 nvme-eui.e8238fa6bf530001001b448b4aec2929-part1 vfat 512M
├─nvme0n1p2 nvme-eui.e8238fa6bf530001001b448b4aec2929-part2 ext4 459.6G
└─nvme0n1p3 nvme-eui.e8238fa6bf530001001b448b4aec2929-part3 swap 16.8G
```
@ -150,10 +150,17 @@ These steps will allow you to update your machine later.
Generate the `hardware-configuration.nix` file for your machine by executing the following command:
```bash
ssh root@flash-installer.local nixos-generate-config --no-filesystems --show-hardware-config > machines/jon/hardware-configuration.nix
clan machines hw-generate [MACHINE_NAME] [HOSTNAME]
```
This command connects to `flash-installer.local` as `root`, runs `nixos-generate-config` to detect hardware configurations (excluding filesystems), and writes them to `machines/jon/hardware-configuration.nix`.
replace `[MACHINE_NAME]` with the name of the machine i.e. `jon` and `[HOSTNAME]` with the `ip_adress` or `hostname` of the machine within the network. i.e. `flash-installer.local`
!!! Example
```bash
clan machines hw-generate jon flash-installer.local
```
This command connects to `flash-installer.local` as `root`, runs `nixos-generate-config` to detect hardware configurations (excluding filesystems), and writes them to `machines/jon/hardware-configuration.nix`.
### Step 3: Custom Disk Formatting

View File

@ -16,7 +16,7 @@ inputs = {
# New flake-parts input
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
clan-core = {
url = "git+https://git.clan.lol/clan/clan-core";
inputs.nixpkgs.follows = "nixpkgs"; # Needed if your configuration uses nixpkgs unstable.
@ -35,7 +35,7 @@ After updating your flake inputs, the next step is to import the `clan-core` fla
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } (
{
#
#
imports = [
inputs.clan-core.flakeModules.default
];
@ -63,7 +63,7 @@ Below is a guide on how to structure this in your flake.nix:
# Define your clan
clan = {
# Clan wide settings. (Required)
clanName = ""; # Ensure to choose a unique name.
meta.name = ""; # Ensure to choose a unique name.
machines = {
jon = {
@ -84,7 +84,7 @@ Below is a guide on how to structure this in your flake.nix:
# There needs to be exactly one controller per clan
clan.networking.zerotier.controller.enable = true;
};
};
};

View File

@ -48,7 +48,7 @@ To introduce a new machine to the VPN, adhere to the following steps:
configuration, substituting `<CONTROLLER>` with the controller machine name:
```nix
{ config, ... }: {
clan.networking.zerotier.networkId = builtins.readFile (config.clanCore.clanDir + "/machines/<CONTROLLER>/facts/zerotier-network-id");
clan.networking.zerotier.networkId = builtins.readFile (config.clan.core.clanDir + "/machines/<CONTROLLER>/facts/zerotier-network-id");
}
```
1. **Update the New Machine**: Execute:

View File

@ -7,11 +7,11 @@
]
},
"locked": {
"lastModified": 1717177033,
"narHash": "sha256-G3CZJafCO8WDy3dyA2EhpUJEmzd5gMJ2IdItAg0Hijw=",
"lastModified": 1718846788,
"narHash": "sha256-9dtXYtEkmXoUJV+PGLqscqF7qTn4AIhAKpFWRFU2NYs=",
"owner": "nix-community",
"repo": "disko",
"rev": "0274af4c92531ebfba4a5bd493251a143bc51f3c",
"rev": "e1174d991944a01eaaa04bc59c6281edca4c0e6e",
"type": "github"
},
"original": {
@ -40,50 +40,21 @@
"type": "github"
}
},
"nixlib": {
"locked": {
"lastModified": 1712450863,
"narHash": "sha256-K6IkdtMtq9xktmYPj0uaYc8NsIqHuaAoRBaMgu9Fvrw=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "3c62b6a12571c9a7f65ab037173ee153d539905f",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixos-2311": {
"locked": {
"lastModified": 1717017538,
"narHash": "sha256-S5kltvDDfNQM3xx9XcvzKEOyN2qk8Sa+aSOLqZ+1Ujc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "64e468fd2652105710d86cd2ae3e65a5a6d58dec",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixos-generators": {
"inputs": {
"nixlib": "nixlib",
"nixlib": [
"nixpkgs"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1716210724,
"narHash": "sha256-iqQa3omRcHGpWb1ds75jS9ruA5R39FTmAkeR3J+ve1w=",
"lastModified": 1718025593,
"narHash": "sha256-WZ1gdKq/9u1Ns/oXuNsDm+W0salonVA0VY1amw8urJ4=",
"owner": "nix-community",
"repo": "nixos-generators",
"rev": "d14b286322c7f4f897ca4b1726ce38cb68596c94",
"rev": "35c20ba421dfa5059e20e0ef2343c875372bdcf3",
"type": "github"
},
"original": {
@ -94,17 +65,17 @@
},
"nixos-images": {
"inputs": {
"nixos-2311": "nixos-2311",
"nixos-stable": [],
"nixos-unstable": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1717040312,
"narHash": "sha256-yI/en4IxuCEClIUpIs3QTyYCCtmSPLOhwLJclfNwdeg=",
"lastModified": 1718845599,
"narHash": "sha256-HbQ0iKohKJC5grC95HNjLxGPdgsc/BJgoENDYNbzkLo=",
"owner": "nix-community",
"repo": "nixos-images",
"rev": "47bfb55316e105390dd761e0b6e8e0be09462b67",
"rev": "c1e6a5f7b08f1c9993de1cfc5f15f838bf783b88",
"type": "github"
},
"original": {
@ -115,11 +86,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1717298511,
"narHash": "sha256-9sXuJn/nL+9ImeYtlspTvjt83z1wIgU+9AwfNbnq+tI=",
"lastModified": 1719451888,
"narHash": "sha256-Ky0sgEEJMcBmNEJztY6KcVn+6bq74EKM7pd1CR1wnPQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6634a0509e9e81e980b129435fbbec518ab246d0",
"rev": "3664857c48feacb35770c00abfdc671e55849be5",
"type": "github"
},
"original": {
@ -148,11 +119,11 @@
"nixpkgs-stable": []
},
"locked": {
"lastModified": 1717297459,
"narHash": "sha256-cZC2f68w5UrJ1f+2NWGV9Gx0dEYmxwomWN2B0lx0QRA=",
"lastModified": 1719111739,
"narHash": "sha256-kr2QzRrplzlCP87ddayCZQS+dhGW98kw2zy7+jUXtF4=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "ab2a43b0d21d1d37d4d5726a892f714eaeb4b075",
"rev": "5e2e9421e9ed2b918be0a441c4535cfa45e04811",
"type": "github"
},
"original": {
@ -168,11 +139,11 @@
]
},
"locked": {
"lastModified": 1717278143,
"narHash": "sha256-u10aDdYrpiGOLoxzY/mJ9llST9yO8Q7K/UlROoNxzDw=",
"lastModified": 1718522839,
"narHash": "sha256-ULzoKzEaBOiLRtjeY3YoGFJMwWSKRYOic6VNw2UyTls=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "3eb96ca1ae9edf792a8e0963cc92fddfa5a87706",
"rev": "68eb1dc333ce82d0ab0c0357363ea17c31ea1f81",
"type": "github"
},
"original": {

View File

@ -15,8 +15,11 @@
sops-nix.inputs.nixpkgs-stable.follows = "";
nixos-generators.url = "github:nix-community/nixos-generators";
nixos-generators.inputs.nixpkgs.follows = "nixpkgs";
nixos-generators.inputs.nixlib.follows = "nixpkgs";
nixos-images.url = "github:nix-community/nixos-images";
nixos-images.inputs.nixos-unstable.follows = "nixpkgs";
# unused input
nixos-images.inputs.nixos-stable.follows = "";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
@ -29,7 +32,7 @@
{ ... }:
{
clan = {
# meta.name = "clan-core";
meta.name = "clan-core";
directory = self;
};
systems = [
@ -49,6 +52,7 @@
./formatter.nix
./lib/flake-module.nix
./nixosModules/flake-module.nix
./nixosModules/clanCore/vars/flake-module.nix
./pkgs/flake-module.nix
./templates/flake-module.nix
];

View File

@ -13,7 +13,6 @@ let
inherit lib clan-core;
inherit (inputs) nixpkgs;
};
cfg = config.clan;
in
{
@ -91,6 +90,11 @@ 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; };
clanModules = lib.mkOption { type = lib.types.attrsOf lib.types.path; };
source = lib.mkOption { type = lib.types.path; };
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); };

View File

@ -10,10 +10,9 @@
treefmt.programs.mypy.enable = true;
treefmt.programs.mypy.directories = {
"pkgs/clan-cli".extraPythonPackages = self'.packages.clan-cli.testDependencies;
"pkgs/clan-vm-manager".extraPythonPackages =
# clan-vm-manager currently only exists on linux
(self'.packages.clan-vm-manager.externalTestDeps or [ ])
++ self'.packages.clan-cli.testDependencies;
"pkgs/clan-app".extraPythonPackages =
# clan-app currently only exists on linux
(self'.packages.clan-app.externalTestDeps or [ ]) ++ self'.packages.clan-cli.testDependencies;
};
treefmt.settings.formatter.nix = {
@ -46,5 +45,53 @@
];
includes = [ "*.py" ];
};
# FIXME: currently broken in CI
#treefmt.settings.formatter.vale =
# let
# vocab = "cLAN";
# style = "Docs";
# config = pkgs.writeText "vale.ini" ''
# StylesPath = ${styles}
# Vocab = ${vocab}
# [*.md]
# BasedOnStyles = Vale, ${style}
# Vale.Terms = No
# '';
# styles = pkgs.symlinkJoin {
# name = "vale-style";
# paths = [
# accept
# headings
# ];
# };
# accept = pkgs.writeTextDir "config/vocabularies/${vocab}/accept.txt" ''
# Nix
# NixOS
# Nixpkgs
# clan.lol
# Clan
# monorepo
# '';
# headings = pkgs.writeTextDir "${style}/headings.yml" ''
# extends: capitalization
# message: "'%s' should be in sentence case"
# level: error
# scope: heading
# # $title, $sentence, $lower, $upper, or a pattern.
# match: $sentence
# '';
# in
# {
# command = "${pkgs.vale}/bin/vale";
# options = [ "--config=${config}" ];
# includes = [ "*.md" ];
# # TODO: too much at once, fix piecemeal
# excludes = [
# "docs/*"
# "clanModules/*"
# "pkgs/*"
# ];
# };
};
}

12
inventory.json Normal file
View File

@ -0,0 +1,12 @@
{
"machines": {
"minimal-inventory-machine": {
"name": "foo",
"system": "x86_64-linux",
"description": "A nice thing",
"icon": "./path/to/icon.png",
"tags": ["1", "2", "3"]
}
},
"services": {}
}

View File

@ -12,73 +12,131 @@
# 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.
# This improves performance, but all nipxkgs.* options will be ignored.
# 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
(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;
system = lib.attrByPath [
"nixpkgs"
"hostSystem"
] null config;
}
) machines;
}
# Will be deprecated
{
machines = lib.mapAttrs (
name: _:
# Use mkForce to make sure users migrate to the inventory system.
# When the settings.json exists the evaluation will print the deprecation warning.
lib.mkForce {
inherit name;
system = (machineSettings name).nixpkgs.hostSystem or null;
}
) 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:
let
warn = lib.warn ''
Usage of Settings.json is only supported for test compatibility.
!!! Consider using the inventory system. !!!
File: ${directory + /machines/${machineName}/settings.json}
If there are still features missing in the inventory system, please open an issue on the clan-core repository.
'';
in
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
# This is useful for doing a dry-run before writing changes into the settings.json
# Using CLAN_MACHINE_SETTINGS_FILE requires passing --impure to nix eval
if builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE" != "" then
builtins.fromJSON (builtins.readFile (builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE"))
warn (builtins.fromJSON (builtins.readFile (builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE")))
else
lib.optionalAttrs (builtins.pathExists "${directory}/machines/${machineName}/settings.json") (
builtins.fromJSON (builtins.readFile (directory + /machines/${machineName}/settings.json))
warn (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 in buildClan is deprecated, please use meta.name instead." 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,16 +156,19 @@ let
clan-core.nixosModules.clanCore
extraConfig
(machines.${name} or { })
# Inherit the inventory assertions ?
{ inherit (mergedInventory) assertions; }
{ imports = serviceConfigs.${name} or { }; }
(
{
# Settings
clanCore.clanDir = directory;
clan.core.clanDir = directory;
# Inherited from clan wide settings
clanCore.clanName = meta.name or clanName;
clanCore.clanIcon = meta.icon or clanIcon;
clan.core.clanName = meta.name or clanName;
clan.core.clanIcon = meta.icon or clanIcon;
# Machine specific settings
clanCore.machineName = name;
clan.core.machineName = name;
networking.hostName = lib.mkDefault name;
nixpkgs.hostPlatform = lib.mkDefault system;
@ -125,7 +186,7 @@ let
} // specialArgs;
};
allMachines = machinesDirs // machines;
allMachines = mergedInventory.machines or { };
supportedSystems = [
"x86_64-linux"
@ -177,9 +238,13 @@ builtins.deepSeq deprecationWarnings {
inherit nixosConfigurations;
clanInternals = {
# Evaluated clan meta
# Merged /clan/meta.json with overrides from buildClan
meta = mergedMeta;
inherit (clan-core) clanModules;
source = "${clan-core}";
meta = mergedInventory.meta;
inventory = mergedInventory;
inventoryFile = "${directory}/inventory.json";
# machine specifics
machines = configsPerSystem;

View File

@ -5,6 +5,8 @@
...
}:
{
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; };

View File

@ -1,5 +1,4 @@
{ lib, clan-core, ... }:
{ clan-core, lib }:
rec {
getReadme =
modulename:
@ -16,18 +15,20 @@ rec {
getShortDescription =
modulename:
let
content = (getReadme modulename);
content = getReadme modulename;
parts = lib.splitString "---" content;
description = builtins.head parts;
number_of_newlines = builtins.length (lib.splitString "\n" description);
# Partition the parts into the first part (the readme content) and the rest (the metadata)
parsed = builtins.partition ({ index, ... }: if index >= 2 then false else true) (
lib.filter ({ index, ... }: index != 0) (lib.imap0 (index: part: { inherit index part; }) parts)
);
# Use this if the content is needed
# readmeContent = lib.concatMapStrings (v: "---" + v.part) parsed.wrong;
meta = builtins.fromTOML (builtins.head parsed.right).part;
in
if (builtins.length parts) > 1 then
if number_of_newlines > 4 then
throw "Short description in README.md for module ${modulename} is too long. Max 3 newlines."
else if number_of_newlines <= 1 then
throw "Missing short description in README.md for module ${modulename}."
else
description
if (builtins.length parts >= 3) then
meta.description
else
throw "Short description delimiter `---` not found in README.md for module ${modulename}";
}

View File

@ -0,0 +1,34 @@
{
nixpkgs,
clan-core,
lib,
}:
let
inherit (import nixpkgs { system = "x86_64-linux"; }) pkgs;
inherit (clan-core) clanModules;
baseModule = {
imports = (import (pkgs.path + "/nixos/modules/module-list.nix")) ++ [
{
nixpkgs.hostPlatform = "x86_64-linux";
clan.core.clanName = "dummy";
}
];
};
# This function takes a list of module names and evaluates them
# evalClanModules :: [ String ] -> { config, options, ... }
evalClanModules =
modulenames:
let
evaled = lib.evalModules {
modules = [
baseModule
clan-core.nixosModules.clanCore
] ++ (map (name: clanModules.${name}) modulenames);
};
in
evaled;
in
evalClanModules

View File

@ -5,9 +5,12 @@
...
}:
{
imports = [ ./jsonschema/flake-module.nix ];
imports = [
./jsonschema/flake-module.nix
./inventory/flake-module.nix
];
flake.lib = import ./default.nix {
inherit lib;
inherit lib inputs;
inherit (inputs) nixpkgs;
clan-core = self;
};

5
lib/inventory/.envrc Normal file
View File

@ -0,0 +1,5 @@
source_up
watch_file flake-module.nix
use flake .#inventory-schema --builders ''

90
lib/inventory/README.md Normal file
View 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"
}
```

View File

@ -0,0 +1,117 @@
# 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
# For error printing
availableTags = lib.foldlAttrs (
acc: _: v:
v.tags or [ ] ++ acc
) [ ] inventory.machines;
tagMembers = builtins.attrNames (
lib.filterAttrs (_n: v: builtins.elem tag v.tags or [ ]) inventory.machines
);
in
if tagMembers == [ ] then
throw "Tag: '${tag}' not found. Available tags: ${builtins.toJSON (lib.unique availableTags)}"
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: machineConfig:
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 "Module doesn't have role: '${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
# Append each machine config
++ [
(lib.optionalAttrs (machineConfig.system or null != null) {
config.nixpkgs.hostPlatform = machineConfig.system;
})
]
) inventory.machines or { };
in
machines

View File

@ -0,0 +1,136 @@
{ 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 =
let
serviceAssertions = 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;
machineAssertions = map (
{ name, value }:
{
assertion = true;
message = "Machine ${name} should define its host system in the inventory. ()";
}
) (lib.attrsToList (lib.filterAttrs (_n: v: v.system or null == null) config.machines));
in
machineAssertions ++ serviceAssertions;
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;
};
system = lib.mkOption {
default = null;
type = t.nullOr 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;
};
}
);
};
}
)
);
};
}

View File

@ -0,0 +1,5 @@
{ lib, clan-core }:
{
buildInventory = import ./build-inventory { inherit lib clan-core; };
interface = ./build-inventory/interface.nix;
}

39
lib/inventory/example.nix Normal file
View File

@ -0,0 +1,39 @@
{ 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" ];
# ... rest of the machine config
};
"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";
};
};
}

View 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": {}
}
}
}
}

View 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"]
}
}
}
}
}
}
}

View 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": {}
}
}
}
}

View File

@ -0,0 +1,81 @@
{ self, inputs, ... }:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
in
{
flake.inventory = import ./example.nix { inherit self; };
perSystem =
{
pkgs,
lib,
config,
system,
...
}:
let
buildInventory = import ./build-inventory {
clan-core = self;
inherit lib;
};
in
{
devShells.inventory-schema = pkgs.mkShell {
inputsFrom = with config.checks; [
lib-inventory-schema
lib-inventory-eval
];
};
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests
legacyPackages.evalTests-inventory = import ./tests {
inherit buildInventory;
clan-core = self;
};
checks = {
lib-inventory-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
export HOME="$(realpath .)"
nix-unit --eval-store "$HOME" \
--extra-experimental-features flakes \
${inputOverrides} \
--flake ${self}#legacyPackages.${system}.evalTests-inventory
touch $out
'';
lib-inventory-schema = pkgs.stdenv.mkDerivation {
name = "inventory-schema-checks";
src = ./.;
buildInputs = [ pkgs.cue ];
buildPhase = ''
echo "Running inventory tests..."
# Cue is easier to run in the same directory as the schema
cd spec
echo "Export cue as json-schema..."
cue export --out openapi root.cue
echo "Validate test/*.json against inventory-schema..."
test_dir="../examples"
for file in "$test_dir"/*; do
# Check if the item is a file
if [ -f "$file" ]; then
# Print the filename
echo "Running test on: $file"
# Run the cue vet command
cue vet "$file" root.cue -d "#Root"
fi
done
touch $out
'';
};
};
};
}

View File

@ -0,0 +1,2 @@
module: "clan.lol/inventory"
language: version: "v0.8.2"

View File

@ -0,0 +1,23 @@
package inventory
import (
"clan.lol/inventory/schema"
)
@jsonschema(schema="http://json-schema.org/schema#")
#Root: {
meta: {
// A name of the clan (primarily shown by the UI)
name: string
// A description of the clan
description?: string
// The icon path
icon?: string
}
// // A map of services
schema.#service
// // A map of machines
schema.#machine
}

View File

@ -0,0 +1,39 @@
package schema
#machine: machines: [string]: {
name: string,
description?: string,
icon?: string
tags: [...string]
}
#role: string
#service: services: [string]: [string]: {
// Required meta fields
meta: {
name: string,
icon?: string
description?: string,
},
// We moved the machine sepcific config to "machines".
// It may be moved back depending on what makes more sense in the future.
roles: [#role]: {
machines: [...string],
tags: [...string],
}
machines?: {
[string]: {
config?: {
...
}
}
},
// Global Configuration for the service
config?: {
// Schema depends on the module.
// It declares the interface how the service can be configured.
...
}
}

View File

@ -0,0 +1,155 @@
{ buildInventory, clan-core, ... }:
{
test_inventory_empty = {
# Empty inventory should return an empty module
expr = buildInventory { };
expected = { };
};
test_inventory_role_imports =
let
configs = buildInventory {
services = {
borgbackup.instance_1 = {
roles.server.machines = [ "backup_server" ];
roles.client.machines = [
"client_1_machine"
"client_2_machine"
];
};
};
machines = {
"backup_server" = { };
"client_1_machine" = { };
"client_2_machine" = { };
};
};
in
{
expr = {
server_imports = (builtins.head configs."backup_server").imports;
client_1_imports = (builtins.head configs."client_1_machine").imports;
client_2_imports = (builtins.head configs."client_2_machine").imports;
};
expected = {
server_imports = [
clan-core.clanModules.borgbackup
"${clan-core.clanModules.borgbackup}/roles/server.nix"
];
client_1_imports = [
clan-core.clanModules.borgbackup
"${clan-core.clanModules.borgbackup}/roles/client.nix"
];
client_2_imports = [
clan-core.clanModules.borgbackup
"${clan-core.clanModules.borgbackup}/roles/client.nix"
];
};
};
test_inventory_tag_resolve =
let
configs = buildInventory {
services = {
borgbackup.instance_1 = {
roles.client.tags = [ "backup" ];
};
};
machines = {
"not_used_machine" = { };
"client_1_machine" = {
tags = [ "backup" ];
};
"client_2_machine" = {
tags = [ "backup" ];
};
};
};
in
{
expr = {
# A machine that includes the backup service should have 3 imports
# - one for some service agnostic properties of the machine itself
# - One for the service itself (default.nix)
# - one for the role (roles/client.nix)
client_1_machine = builtins.length configs.client_1_machine;
client_2_machine = builtins.length configs.client_2_machine;
not_used_machine = builtins.length configs.not_used_machine;
};
expected = {
client_1_machine = 3;
client_2_machine = 3;
not_used_machine = 1;
};
};
test_inventory_multiple_roles =
let
configs = buildInventory {
services = {
borgbackup.instance_1 = {
roles.client.machines = [ "machine_1" ];
roles.server.machines = [ "machine_1" ];
};
};
machines = {
"machine_1" = { };
};
};
in
{
expr = {
machine_1_imports = (builtins.head configs."machine_1").imports;
};
expected = {
machine_1_imports = [
clan-core.clanModules.borgbackup
"${clan-core.clanModules.borgbackup}/roles/client.nix"
"${clan-core.clanModules.borgbackup}/roles/server.nix"
];
};
};
test_inventory_role_doesnt_exist =
let
configs = buildInventory {
services = {
borgbackup.instance_1 = {
roles.roleXYZ.machines = [ "machine_1" ];
};
};
machines = {
"machine_1" = { };
};
};
in
{
expr = configs;
expectedError = {
type = "ThrownError";
msg = "Module doesn't have role.*";
};
};
test_inventory_tag_doesnt_exist =
let
configs = buildInventory {
services = {
borgbackup.instance_1 = {
roles.client.machines = [ "machine_1" ];
roles.client.tags = [ "tagXYZ" ];
};
};
machines = {
"machine_1" = {
tags = [ "tagABC" ];
};
};
};
in
{
expr = configs;
expectedError = {
type = "ThrownError";
msg = "Tag: '\\w+' not found";
};
};
}

View File

@ -47,7 +47,7 @@ rec {
let
evaled = lib.evalModules { modules = [ module ]; };
in
parseOptions evaled.options;
{ "$schema" = "http://json-schema.org/draft-07/schema#"; } // parseOptions evaled.options;
# parses a set of evaluated nixos options to a jsonschema
parseOptions =
@ -66,6 +66,7 @@ rec {
// {
type = "object";
inherit properties;
additionalProperties = false;
};
# parses and evaluated nixos option to a jsonschema property definition

View File

@ -1,5 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
@ -38,6 +40,7 @@
},
"services": {
"type": "object",
"additionalProperties": false,
"properties": {
"opt": {
"type": "string",
@ -59,9 +62,8 @@
"type": "string"
}
},
"required": [
"repo"
],
"required": ["repo"],
"additionalProperties": false,
"type": "object"
},
"default": {},

View File

@ -278,6 +278,7 @@ in
expr = slib.parseOption (evalType (lib.types.submodule subModule) { });
expected = {
type = "object";
additionalProperties = false;
properties = {
opt = {
type = "boolean";
@ -301,6 +302,7 @@ in
expr = slib.parseOption (evalType (lib.types.submodule subModule) { });
expected = {
type = "object";
additionalProperties = false;
properties = {
opt = {
type = "boolean";
@ -331,6 +333,7 @@ in
type = "object";
additionalProperties = {
type = "object";
additionalProperties = false;
properties = {
opt = {
type = "boolean";
@ -363,6 +366,7 @@ in
type = "array";
items = {
type = "object";
additionalProperties = false;
properties = {
opt = {
type = "boolean";

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