From df73169392456367d6abd7d19f08aed1e4d58281 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Thu, 16 Oct 2025 18:26:21 +0200 Subject: [PATCH 1/4] clanServices: Add missing manifest.readme to some services --- .../service-dummy-test-from-flake/flake.nix | 1 + checks/service-dummy-test/default.nix | 1 + clanServices/admin/README.md | 25 ++++++++++++++++++ clanServices/admin/default.nix | 1 + clanServices/garage/README.md | 12 +++++++++ clanServices/garage/default.nix | 1 + clanServices/hello-world/README.md | 1 + clanServices/hello-world/default.nix | 1 + clanServices/matrix-synapse/README.md | 23 ++++++++++++++++ clanServices/matrix-synapse/default.nix | 1 + clanServices/mycelium/README.md | 20 ++++++++++++++ clanServices/mycelium/default.nix | 1 + clanServices/syncthing/README.md | 26 +++++++++++-------- clanServices/yggdrasil/README.md | 9 +++---- clanServices/yggdrasil/default.nix | 1 + 15 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 clanServices/admin/README.md create mode 100644 clanServices/garage/README.md create mode 100644 clanServices/hello-world/README.md create mode 100644 clanServices/matrix-synapse/README.md create mode 100644 clanServices/mycelium/README.md diff --git a/checks/service-dummy-test-from-flake/flake.nix b/checks/service-dummy-test-from-flake/flake.nix index ec0621d81..83077e566 100644 --- a/checks/service-dummy-test-from-flake/flake.nix +++ b/checks/service-dummy-test-from-flake/flake.nix @@ -27,6 +27,7 @@ modules.new-service = { _class = "clan.service"; manifest.name = "new-service"; + manifest.readme = "Just a sample readme to not trigger the warning."; roles.peer = { description = "A peer that uses the new-service to generate some files."; }; diff --git a/checks/service-dummy-test/default.nix b/checks/service-dummy-test/default.nix index 7f16945bd..b31c24218 100644 --- a/checks/service-dummy-test/default.nix +++ b/checks/service-dummy-test/default.nix @@ -34,6 +34,7 @@ nixosLib.runTest ( modules.new-service = { _class = "clan.service"; manifest.name = "new-service"; + manifest.readme = "Just a sample readme to not trigger the warning."; roles.peer = { description = "A peer that uses the new-service to generate some files."; }; diff --git a/clanServices/admin/README.md b/clanServices/admin/README.md new file mode 100644 index 000000000..fc68cd792 --- /dev/null +++ b/clanServices/admin/README.md @@ -0,0 +1,25 @@ +The admin service aggregates components that allow an administrator to log in to and manage the machine. + +The following configuration: + +1. Enables OpenSSH with root login and adds an SSH public key named`myusersKey` to the machine's authorized_keys via the `allowedKeys` setting. + +2. Automatically generates a password for the root user. + +```nix +instances = { + admin = { + roles.default.tags = { + all = { }; + }; + roles.default.settings = { + allowedKeys = { + myusersKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFDNnynMbFWatSFdANzbJ8iiEKL7+9ZpDaMLrWRQjyH lhebendanz@wintux"; + }; + }; + }; +}; +``` + + + diff --git a/clanServices/admin/default.nix b/clanServices/admin/default.nix index 89ab46cae..434d21c10 100644 --- a/clanServices/admin/default.nix +++ b/clanServices/admin/default.nix @@ -3,6 +3,7 @@ manifest.name = "clan-core/admin"; manifest.description = "Adds a root user with ssh access"; manifest.categories = [ "Utility" ]; + manifest.readme = builtins.readFile ./README.md; roles.default = { description = "Placeholder role to apply the admin service"; diff --git a/clanServices/garage/README.md b/clanServices/garage/README.md new file mode 100644 index 000000000..9533d1da9 --- /dev/null +++ b/clanServices/garage/README.md @@ -0,0 +1,12 @@ +[Garage](https://garagehq.deuxfleurs.fr/) is an open-source, S3-compatible distributed object storage service for self-hosting. + +This module provisions a single-instance S3 bucket. To customize its behavior, set `services.garage.settings` in your Nix configuration. + +Example configuration: +``` +instances = { + garage = { + roles.default.machines."server" = {}; + }; +}; +``` diff --git a/clanServices/garage/default.nix b/clanServices/garage/default.nix index b796e63b5..c84d27a5f 100644 --- a/clanServices/garage/default.nix +++ b/clanServices/garage/default.nix @@ -4,6 +4,7 @@ manifest.name = "clan-core/garage"; manifest.description = "S3-compatible object store for small self-hosted geo-distributed deployments"; manifest.categories = [ "System" ]; + manifest.readme = builtins.readFile ./README.md; roles.default = { description = "Placeholder role to apply the garage service"; diff --git a/clanServices/hello-world/README.md b/clanServices/hello-world/README.md new file mode 100644 index 000000000..04c30234b --- /dev/null +++ b/clanServices/hello-world/README.md @@ -0,0 +1 @@ +This a test README just to appease the eval warnings if we don't have one \ No newline at end of file diff --git a/clanServices/hello-world/default.nix b/clanServices/hello-world/default.nix index f64857d4a..d14e5b057 100644 --- a/clanServices/hello-world/default.nix +++ b/clanServices/hello-world/default.nix @@ -9,6 +9,7 @@ _class = "clan.service"; manifest.name = "clan-core/hello-word"; manifest.description = "This is a test"; + manifest.readme = builtins.readFile ./README.md; # This service provides two roles: "morning" and "evening". Roles can be # defined in this file directly (e.g. the "morning" role) or split up into a diff --git a/clanServices/matrix-synapse/README.md b/clanServices/matrix-synapse/README.md new file mode 100644 index 000000000..15cc3567c --- /dev/null +++ b/clanServices/matrix-synapse/README.md @@ -0,0 +1,23 @@ +This NixOS module installs and configures Synapse — a federated Matrix homeserver with end-to-end encryption — and optionally provides the Element web client. + +The example below demonstrates a minimal setup that includes: + +- Element web client. +- Synapse backed by PostgreSQL and nginx. +- An admin user and an additional regular user. + +Example configuration: + +```nix +instances = { + matrix-synapse = { + roles.default.machines."jon".settings = { + acmeEmail = "admins@clan.lol"; + server_tld = "clan.test"; + app_domain = "matrix.clan.test"; + users.admin.admin = true; + users.someuser = { }; + }; + }; +}; +``` \ No newline at end of file diff --git a/clanServices/matrix-synapse/default.nix b/clanServices/matrix-synapse/default.nix index d5782831c..b686a3c72 100644 --- a/clanServices/matrix-synapse/default.nix +++ b/clanServices/matrix-synapse/default.nix @@ -4,6 +4,7 @@ manifest.name = "clan-core/matrix-synapese"; manifest.description = "A federated messaging server with end-to-end encryption."; manifest.categories = [ "Social" ]; + manifest.readme = builtins.readFile ./README.md; roles.default = { description = "Placeholder role to apply the matrix-synapse service"; diff --git a/clanServices/mycelium/README.md b/clanServices/mycelium/README.md new file mode 100644 index 000000000..e50f37d83 --- /dev/null +++ b/clanServices/mycelium/README.md @@ -0,0 +1,20 @@ + +[Mycelium](https://github.com/threefoldtech/mycelium) is an end-to-end encrypted IPv6 overlay network that spans the globe. + +## Features +- Locality-aware routing: finds the shortest path between nodes. +- All traffic is end-to-end encrypted. +- Can route traffic via friend nodes and is location-aware. +- Automatic rerouting if a physical link goes down. +- IPv6 addresses are derived from private keys. +- A simple, reliable message bus is implemented on top of Mycelium. +- Supports multiple transports (QUIC, TCP, …). Hole punching for QUIC is in progress to enable true P2P connectivity behind NATs. +- Designed for planetary-scale scalability; previous overlay networks reached practical limits, and Mycelium focuses on scaling. +- Can run without a TUN device and be used solely as a reliable message bus. + +Example configuration below connects all your machines to the Mycelium network: +```nix +mycelium = { + roles.peer.tags.all = {}; +}; +``` diff --git a/clanServices/mycelium/default.nix b/clanServices/mycelium/default.nix index 94fd51dc0..c9631d0fd 100644 --- a/clanServices/mycelium/default.nix +++ b/clanServices/mycelium/default.nix @@ -7,6 +7,7 @@ "System" "Network" ]; + manifest.readme = builtins.readFile ./README.md; roles.peer = { description = "A peer in the mycelium network"; diff --git a/clanServices/syncthing/README.md b/clanServices/syncthing/README.md index 4ac4ebc91..18626915f 100644 --- a/clanServices/syncthing/README.md +++ b/clanServices/syncthing/README.md @@ -1,20 +1,24 @@ -## Usage +This service configures Syncthing to continuously synchronize a folder peer-to-peer across your machines. + +Example configuration: ```nix { - instances.syncthing = { - roles.peer.tags.all = { }; - roles.peer.settings.folders = { - documents = { - path = "~/syncthing/documents"; - }; - }; + instances.syncthing = { + roles.peer.tags.all = { }; + roles.peer.settings.folders = { + documents = { + path = "/home/youruser/syncthing/documents"; }; + }; + }; } ``` -Now the folder `~/syncthing/documents` will be shared and kept in sync with all your machines. +Notes: +- Each key under `folders` is a folder ID (an arbitrary identifier for Syncthing). +- Prefer absolute paths (example shown). `~` may work in some environments but can be ambiguous in service contexts. -## Documentation -Extensive documentation is available on the [Syncthing](https://docs.syncthing.net/) website. +## Documentation +See the official Syncthing docs: https://docs.syncthing.net/ diff --git a/clanServices/yggdrasil/README.md b/clanServices/yggdrasil/README.md index 73f7cab85..c639b10cb 100644 --- a/clanServices/yggdrasil/README.md +++ b/clanServices/yggdrasil/README.md @@ -1,14 +1,11 @@ -This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across -your clan. +This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan. Yggdrasil is designed to be a future-proof and decentralised alternative to -the structured routing protocols commonly used today on the internet. Inside -your clan, it will allow you to reach all of your machines. +the structured routing protocols commonly used today on the internet. Inside your clan, it will allow you to reach all of your machines. ## Example Usage -While you can specify statically configured peers for each host, yggdrasil does -auto-discovery of local peers. +While you can specify statically configured peers for each host, yggdrasil does auto-discovery of local peers. ```nix inventory = { diff --git a/clanServices/yggdrasil/default.nix b/clanServices/yggdrasil/default.nix index 349b156f2..812b1b50a 100644 --- a/clanServices/yggdrasil/default.nix +++ b/clanServices/yggdrasil/default.nix @@ -3,6 +3,7 @@ _class = "clan.service"; manifest.name = "clan-core/yggdrasil"; manifest.description = "Yggdrasil encrypted IPv6 routing overlay network"; + manifest.readme = builtins.readFile ./README.md; roles.default = { description = "Placeholder role to apply the yggdrasil service"; From 7a4a940e839289199b01d61c2ff8aa09ea4756e0 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Thu, 16 Oct 2025 18:53:22 +0200 Subject: [PATCH 2/4] clanServices: add READMEs to internet and tor modules --- clanServices/internet/README.md | 19 +++++++++++++++++++ clanServices/internet/default.nix | 2 ++ clanServices/packages/README.md | 11 +++++++++++ clanServices/packages/default.nix | 1 + clanServices/tor/README.md | 14 ++++++++++++++ clanServices/tor/default.nix | 1 + 6 files changed, 48 insertions(+) create mode 100644 clanServices/internet/README.md create mode 100644 clanServices/packages/README.md create mode 100644 clanServices/tor/README.md diff --git a/clanServices/internet/README.md b/clanServices/internet/README.md new file mode 100644 index 000000000..83a980b54 --- /dev/null +++ b/clanServices/internet/README.md @@ -0,0 +1,19 @@ +This module is part of Clan's [networking interface](https://docs.clan.lol/guides/networking/networking/). + +Clan's networking module automatically manages connections across available network transports and falls back intelligently. When you run `clan ssh` or `clan machines update`, Clan attempts each configured network in priority order until a connection succeeds. + +The example below shows how to configure a domain so server1 is reachable over the clearnet. By default, the `internet` module has the highest priority among networks. + +```nix + inventory.instances = { + # Direct SSH with fallback support + internet = { + roles.default.machines.server1 = { + settings.host = "server1.example.com"; + }; + roles.default.machines.server2 = { + settings.host = "192.168.1.100"; + }; + }; +}; +``` \ No newline at end of file diff --git a/clanServices/internet/default.nix b/clanServices/internet/default.nix index 575a41d7a..ebc4f9ec4 100644 --- a/clanServices/internet/default.nix +++ b/clanServices/internet/default.nix @@ -7,6 +7,8 @@ "System" "Network" ]; + manifest.readme = builtins.readFile ./README.md; + roles.default = { description = "Placeholder role to apply the internet service"; interface = diff --git a/clanServices/packages/README.md b/clanServices/packages/README.md new file mode 100644 index 000000000..5f0616720 --- /dev/null +++ b/clanServices/packages/README.md @@ -0,0 +1,11 @@ +This service is meant to be consumed by the UI / API, and exposes a JSON serializable interface to add packages to a machine over the inventory. + +The example below demonstrates installing the "cbonsai" application to a machine named "server. + +``` +instances.packages = { + roles.default.machines."server".settings = { + packages = [ "cbonsai" ]; + }; +}; +``` \ No newline at end of file diff --git a/clanServices/packages/default.nix b/clanServices/packages/default.nix index 795e59b6e..1ba45c4d3 100644 --- a/clanServices/packages/default.nix +++ b/clanServices/packages/default.nix @@ -6,6 +6,7 @@ manifest.categories = [ "System" ]; + manifest.readme = builtins.readFile ./README.md; roles.default = { description = "Placeholder role to apply the packages service"; diff --git a/clanServices/tor/README.md b/clanServices/tor/README.md new file mode 100644 index 000000000..ff31b1acd --- /dev/null +++ b/clanServices/tor/README.md @@ -0,0 +1,14 @@ +This module is part of Clan's [networking interface](https://docs.clan.lol/guides/networking/networking/). + +Clan's networking module automatically manages connections across available network transports and falls back intelligently. When you run `clan ssh` or `clan machines update`, Clan attempts each configured network in priority order until a connection succeeds. + +The example below configures all your nixos machines to be reachable over the Tor network. By default, the `tor` module has the lowest priority among networks, as it's the slowest. + +```nix + inventory.instances = { + # Fallback: Secure connections via Tor + tor = { + roles.server.tags.nixos = { }; + }; +}; +``` \ No newline at end of file diff --git a/clanServices/tor/default.nix b/clanServices/tor/default.nix index 2e29471fd..f28660c0d 100644 --- a/clanServices/tor/default.nix +++ b/clanServices/tor/default.nix @@ -7,6 +7,7 @@ "System" "Network" ]; + manifest.readme = builtins.readFile ./README.md; roles.client = { description = '' From 18dc042a0b2c4b9fe8180145ca9f93da637909a5 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Thu, 16 Oct 2025 18:26:45 +0200 Subject: [PATCH 3/4] inventory: Add warning if manifest.readme is missing --- lib/modules/inventory/distributed-service/service-module.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/modules/inventory/distributed-service/service-module.nix b/lib/modules/inventory/distributed-service/service-module.nix index 77db7d53f..13ae05355 100644 --- a/lib/modules/inventory/distributed-service/service-module.nix +++ b/lib/modules/inventory/distributed-service/service-module.nix @@ -979,9 +979,12 @@ in else null ) config.roles; + manifestWarnings = lib.optionals (config.manifest.readme == null || config.manifest.readme == "") [ + "Missing manifest.readme for clanService '${formatModule}'" + ]; in { - warnings = (lib.filter (v: v != null) warningsWithNull); + warnings = (lib.filter (v: v != null) warningsWithNull ++ manifestWarnings); assertions = lib.attrValues failedAssertions; } ) From 2ac65b9c83cf68583d3904f0c88a4e3263cde141 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Thu, 16 Oct 2025 18:27:24 +0200 Subject: [PATCH 4/4] clan_lib/modules.py: get_service_readmes now logs an error if the readme is empty --- pkgs/clan-cli/clan_lib/services/modules.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkgs/clan-cli/clan_lib/services/modules.py b/pkgs/clan-cli/clan_lib/services/modules.py index 82fe8a9c9..629a489e6 100644 --- a/pkgs/clan-cli/clan_lib/services/modules.py +++ b/pkgs/clan-cli/clan_lib/services/modules.py @@ -1,3 +1,4 @@ +import logging import re import tomllib from dataclasses import dataclass, field, fields @@ -16,6 +17,8 @@ from clan_lib.nix_models.clan import ( from clan_lib.persist.inventory_store import InventoryStore from clan_lib.persist.path_utils import get_value_by_path, set_value_by_path +log = logging.getLogger(__name__) + class CategoryInfo(TypedDict): color: str @@ -242,6 +245,11 @@ def get_service_readmes( readmes = flake.select(query) + for name, content in readmes.items(): + if content is None or content.strip() == "": + readmes[name] = None + log.error(f"Service '{name}' is missing manifest.readme field") + return ServiceReadmeCollection(input_name=input_name, readmes=readmes)