Merge pull request 'clanServices: add warning if manifest.readme is not set' (#5537) from Qubasa/clan-core:fix_manifest_readmes into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5537
This commit is contained in:
Luis Hebendanz
2025-10-16 17:12:46 +00:00
23 changed files with 167 additions and 18 deletions

View File

@@ -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.";
};

View File

@@ -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.";
};

View File

@@ -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";
};
};
};
};
```

View File

@@ -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";

View File

@@ -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" = {};
};
};
```

View File

@@ -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";

View File

@@ -0,0 +1 @@
This a test README just to appease the eval warnings if we don't have one

View File

@@ -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

View File

@@ -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";
};
};
};
```

View File

@@ -7,6 +7,8 @@
"System"
"Network"
];
manifest.readme = builtins.readFile ./README.md;
roles.default = {
description = "Placeholder role to apply the internet service";
interface =

View File

@@ -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 = { };
};
};
};
```

View File

@@ -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";

View File

@@ -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 = {};
};
```

View File

@@ -7,6 +7,7 @@
"System"
"Network"
];
manifest.readme = builtins.readFile ./README.md;
roles.peer = {
description = "A peer in the mycelium network";

View File

@@ -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" ];
};
};
```

View File

@@ -6,6 +6,7 @@
manifest.categories = [
"System"
];
manifest.readme = builtins.readFile ./README.md;
roles.default = {
description = "Placeholder role to apply the packages service";

View File

@@ -1,4 +1,6 @@
## Usage
This service configures Syncthing to continuously synchronize a folder peer-to-peer across your machines.
Example configuration:
```nix
{
@@ -6,15 +8,17 @@
roles.peer.tags.all = { };
roles.peer.settings.folders = {
documents = {
path = "~/syncthing/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.
See the official Syncthing docs: https://docs.syncthing.net/

View File

@@ -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 = { };
};
};
```

View File

@@ -7,6 +7,7 @@
"System"
"Network"
];
manifest.readme = builtins.readFile ./README.md;
roles.client = {
description = ''

View File

@@ -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 = {

View File

@@ -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";

View File

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

View File

@@ -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)