Compare commits
12 Commits
deps-poc
...
simplify_h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
864b131010 | ||
|
|
ee0f111fc9 | ||
|
|
1b193123b2 | ||
|
|
81126da053 | ||
|
|
67795730a2 | ||
|
|
e6797c6f20 | ||
|
|
93280a9f98 | ||
|
|
d89ddfabec | ||
|
|
e2946615f0 | ||
|
|
bce9f9a747 | ||
|
|
b494bdee21 | ||
|
|
13632ff659 |
@@ -8,7 +8,7 @@ The service consists of two roles:
|
||||
- A `server` role: This is the DNS-server that will be queried when trying to
|
||||
resolve clan-internal services. It defines the top-level domain.
|
||||
- A `default` role: This does two things. First, it sets up the nameservers so
|
||||
thatclan-internal queries are resolved via the `server` machine, while
|
||||
that clan-internal queries are resolved via the `server` machine, while
|
||||
external queries are resolved as normal via DHCP. Second, it allows exposing
|
||||
services (see example below).
|
||||
|
||||
|
||||
@@ -3,20 +3,13 @@
|
||||
|
||||
# The test for this module in ./tests/vm/default.nix shows an example of how
|
||||
# the service is used.
|
||||
{ ... }@service:
|
||||
|
||||
{ packages }:
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/hello-world";
|
||||
manifest.name = "clan-core/hello-word";
|
||||
manifest.description = "This is a test";
|
||||
manifest.dependencies = {
|
||||
#
|
||||
home-manager = {
|
||||
recomendedUrl = "github:nix-community/home-manager/release-25.05";
|
||||
};
|
||||
};
|
||||
|
||||
# Declare dependencies that the user must provide via flake inputs
|
||||
# Or via 'clan.serviceOverrides.<manifest-name>.dependencies.resolved
|
||||
|
||||
# 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
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
module = ./default.nix;
|
||||
module = lib.modules.importApply ./default.nix {
|
||||
inherit (self) packages;
|
||||
};
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
|
||||
12
devFlake/flake.lock
generated
12
devFlake/flake.lock
generated
@@ -84,11 +84,11 @@
|
||||
},
|
||||
"nixpkgs-dev": {
|
||||
"locked": {
|
||||
"lastModified": 1757752761,
|
||||
"narHash": "sha256-HBM2YTKSegLZjdamfqH9KADj2zQBQBNQHmwdrYkatpg=",
|
||||
"lastModified": 1757924820,
|
||||
"narHash": "sha256-to/hwbY9/jsRaejPa5oJmPUFZsJfFCB3WReKhD0+/+E=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4b46c744cbd5f9336027dff287e74ead84d80041",
|
||||
"rev": "aa54acd34af0e86f49d55ea52823031e2da399df",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -107,11 +107,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1757624466,
|
||||
"narHash": "sha256-25ExS2AkQD05Jf0Y2Wnn5KHpucN2d3ObEQOVaDh7ubg=",
|
||||
"lastModified": 1757885130,
|
||||
"narHash": "sha256-56CMb5W/pgjKLh0bx2ekhn5rde/YmgR63HAqrY9/BCw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "da8bcb74407e41d334fc79081fdd8948b795bd6f",
|
||||
"rev": "fae3c59a646e00c4b1d359c50b27458a0713d2fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
8
flake.lock
generated
8
flake.lock
generated
@@ -13,11 +13,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1757300813,
|
||||
"narHash": "sha256-JYQl+8nJYImg/inqotu9nEPcTXrRJixFN6sOfn6Tics=",
|
||||
"rev": "b5f2157bcd26c73551374cd6e5b027b0119b2f3d",
|
||||
"lastModified": 1757905600,
|
||||
"narHash": "sha256-Yd7buL9N7N7IaDVViItqP9HsECfnlDFykxvvNgMYcKk=",
|
||||
"rev": "c10c4002bdc5aef040fcbb814d5f482e82dc8345",
|
||||
"type": "tarball",
|
||||
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/b5f2157bcd26c73551374cd6e5b027b0119b2f3d.tar.gz"
|
||||
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/c10c4002bdc5aef040fcbb814d5f482e82dc8345.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
|
||||
32
flake.nix
32
flake.nix
@@ -66,39 +66,7 @@
|
||||
};
|
||||
clan = {
|
||||
meta.name = "clan-core";
|
||||
|
||||
modules = {
|
||||
myModule = { ... }: {
|
||||
#
|
||||
_module.args = { inherit inputs; };
|
||||
};
|
||||
|
||||
# # depends on home-manager 25.05
|
||||
# myEnzime = { ... }: {
|
||||
# imports = [ inputs.enzime.yours ];
|
||||
# dependencies.home-manager = lib.mkForce "my-home-manager";
|
||||
# };
|
||||
# # depends on home-manager 24.05
|
||||
# myLassulus = { ... }: {
|
||||
# imports = [ inputs.lassulus.his ];
|
||||
# dependencies.home-manager = lib.mkForce "my-home-manager";
|
||||
# };
|
||||
};
|
||||
|
||||
|
||||
serviceOverrides = {
|
||||
"clan-core/hello-world" = {
|
||||
dependencies = {
|
||||
flake-parts = "flake-parts";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
inventory = {
|
||||
instances.hello-world = {
|
||||
roles.morning.tags = [ "all" ];
|
||||
};
|
||||
|
||||
machines = {
|
||||
"test-darwin-machine" = {
|
||||
machineClass = "darwin";
|
||||
|
||||
@@ -228,38 +228,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
serviceOverrides = lib.mkOption {
|
||||
type = types.attrsOf (types.submoduleWith {
|
||||
modules = [
|
||||
{
|
||||
options.dependencies = lib.mkOption {
|
||||
type = types.attrsOf types.raw;
|
||||
description = "Override a dependencies of this service";
|
||||
};
|
||||
}
|
||||
];
|
||||
});
|
||||
default = { };
|
||||
description = ''
|
||||
Override/inject dependencies to a service.
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
{
|
||||
servicesOverrides = {
|
||||
# Override need to be done by manifest name to avoid ambiguity
|
||||
"clan-core/hello-world" = {
|
||||
dependencies = {
|
||||
home-manager = inputs.home-manager-v2;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
'';
|
||||
};
|
||||
|
||||
inventory = lib.mkOption {
|
||||
type = types.submoduleWith {
|
||||
modules = [
|
||||
|
||||
@@ -248,7 +248,7 @@ in
|
||||
staticModules = clan-core.clan.modules;
|
||||
|
||||
distributedServices = clanLib.inventory.mapInstances {
|
||||
inherit (clanConfig) inventory exportsModule serviceOverrides;
|
||||
inherit (clanConfig) inventory exportsModule;
|
||||
inherit flakeInputs directory;
|
||||
clanCoreModules = clan-core.clan.modules;
|
||||
prefix = [ "distributedServices" ];
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
# TODO: consume directly from clan.config
|
||||
directory,
|
||||
serviceOverrides,
|
||||
}:
|
||||
{
|
||||
lib,
|
||||
@@ -32,12 +31,10 @@ in
|
||||
(
|
||||
{ name, ... }:
|
||||
{
|
||||
_module.args = {
|
||||
_ctx = [ name ];
|
||||
exports = config.exports;
|
||||
directory = directory;
|
||||
inherit (specialArgs) clanLib _unsafe;
|
||||
};
|
||||
_module.args._ctx = [ name ];
|
||||
_module.args.exports = config.exports;
|
||||
_module.args.directory = directory;
|
||||
|
||||
}
|
||||
)
|
||||
./service-module.nix
|
||||
@@ -46,9 +43,6 @@ in
|
||||
inherit (specialArgs) clanLib;
|
||||
prefix = _ctx;
|
||||
})
|
||||
(service: {
|
||||
dependencies = lib.mapAttrs (n: v: { resolved = v; }) serviceOverrides.${service.config.manifest.name}.dependencies or { };
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -26,9 +26,8 @@ in
|
||||
inventory,
|
||||
directory,
|
||||
clanCoreModules,
|
||||
exportsModule,
|
||||
prefix ? [ ],
|
||||
serviceOverrides ? { },
|
||||
exportsModule,
|
||||
}:
|
||||
let
|
||||
# machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags;
|
||||
@@ -128,10 +127,9 @@ in
|
||||
specialArgs = {
|
||||
inherit clanLib;
|
||||
_ctx = prefix;
|
||||
_unsafe.flakeInputs = flakeInputs;
|
||||
};
|
||||
modules = [
|
||||
(import ./all-services-wrapper.nix { inherit directory serviceOverrides; })
|
||||
(import ./all-services-wrapper.nix { inherit directory; })
|
||||
]
|
||||
++ modules;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
lib,
|
||||
config,
|
||||
_ctx,
|
||||
_unsafe,
|
||||
directory,
|
||||
exports,
|
||||
...
|
||||
@@ -107,10 +106,6 @@ let
|
||||
in
|
||||
{
|
||||
options = {
|
||||
|
||||
debug = mkOption {
|
||||
default = _unsafe.flakeInputs;
|
||||
};
|
||||
# Option to disable some behavior during docs rendering
|
||||
_docs_rendering = mkOption {
|
||||
default = false;
|
||||
@@ -118,54 +113,6 @@ in
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
dependencies = mkOption {
|
||||
type = types.attrsWith {
|
||||
placeholder = "dependencyName";
|
||||
elemType = types.submoduleWith {
|
||||
modules = [
|
||||
({name,...}@dep: {
|
||||
options.name = mkOption {
|
||||
default = name;
|
||||
type = types.str;
|
||||
description = "The name of the dependency, usually the input name.";
|
||||
};
|
||||
options.resolved = mkOption {
|
||||
type = types.raw;
|
||||
default = _unsafe.flakeInputs.${dep.config.name} or (throw ''
|
||||
The dependency '${dep.config.name}' could not be found in the flake inputs.
|
||||
|
||||
This module requires '${dep.config.name}' to be present
|
||||
Fixes:
|
||||
- Add '${dep.config.name}' to the flake inputs
|
||||
- Inject a custom dependency via 'clan.serviceOverrides.<manifest-name>.dependencies.${dep.config.name} = ...'
|
||||
'');
|
||||
description = ''
|
||||
The resolved value of the dependency.
|
||||
'';
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Dependencies of this service.
|
||||
|
||||
Can be declared via `clan.lib.mkDependency`.
|
||||
|
||||
```nix
|
||||
{
|
||||
home-manager = clan.lib.mkDependency {
|
||||
name = "home-manager";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This will map `inputs.home-manager` to `dependencies.home-manager`.
|
||||
The dependency can then be safely accessed via `config.dependencies.home-manager` from the toplevel arguments of this module.
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
instances = mkOption {
|
||||
visible = false;
|
||||
defaultText = "Throws: 'The service must define its instances' when not defined";
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import logging
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import ExitStack
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
|
||||
from clan_lib.api import ApiError, ApiResponse, ErrorDataClass
|
||||
from clan_lib.api.tasks import WebThread
|
||||
from clan_lib.async_run import set_current_thread_opkey, set_should_cancel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .middleware import Middleware
|
||||
from clan_app.middleware.base import Middleware
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,20 +29,17 @@ class BackendResponse:
|
||||
_op_key: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApiBridge(ABC):
|
||||
class ApiBridge(Protocol):
|
||||
"""Generic interface for API bridges that can handle method calls from different sources."""
|
||||
|
||||
middleware_chain: tuple["Middleware", ...]
|
||||
threads: dict[str, WebThread] = field(default_factory=dict)
|
||||
threads: dict[str, WebThread]
|
||||
|
||||
@abstractmethod
|
||||
def send_api_response(self, response: BackendResponse) -> None:
|
||||
"""Send response back to the client."""
|
||||
def send_api_response(self, response: BackendResponse) -> None: ...
|
||||
|
||||
def process_request(self, request: BackendRequest) -> None:
|
||||
"""Process an API request through the middleware chain."""
|
||||
from .middleware import MiddlewareContext # noqa: PLC0415
|
||||
from clan_app.middleware.base import MiddlewareContext # noqa: PLC0415
|
||||
|
||||
with ExitStack() as stack:
|
||||
context = MiddlewareContext(
|
||||
|
||||
20
pkgs/clan-app/clan_app/api/middleware.py
Normal file
20
pkgs/clan-app/clan_app/api/middleware.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Compatibility wrapper for relocated middleware components.
|
||||
|
||||
This module preserves the legacy import path ``clan_app.api.middleware`` while
|
||||
the actual middleware implementations now live in ``clan_app.middleware``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from warnings import warn
|
||||
|
||||
import clan_app.middleware as _middleware
|
||||
from clan_app.middleware import * # noqa: F403
|
||||
|
||||
warn(
|
||||
"clan_app.api.middleware is deprecated; use clan_app.middleware instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
__all__ = _middleware.__all__
|
||||
@@ -12,13 +12,13 @@ from clan_lib.log_manager import LogGroupConfig, LogManager
|
||||
from clan_lib.log_manager import api as log_manager_api
|
||||
|
||||
from clan_app.api.file_gtk import get_clan_folder, get_system_file
|
||||
from clan_app.api.middleware import (
|
||||
from clan_app.deps.http.http_server import HttpApiServer
|
||||
from clan_app.deps.webview.webview import Size, SizeHint, Webview
|
||||
from clan_app.middleware import (
|
||||
ArgumentParsingMiddleware,
|
||||
LoggingMiddleware,
|
||||
MethodExecutionMiddleware,
|
||||
)
|
||||
from clan_app.deps.http.http_server import HttpApiServer
|
||||
from clan_app.deps.webview.webview import Size, SizeHint, Webview
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from clan_lib.async_run import (
|
||||
from clan_app.api.api_bridge import ApiBridge, BackendRequest, BackendResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from clan_app.api.middleware import Middleware
|
||||
from clan_app.middleware.base import Middleware
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from clan_lib.api import MethodRegistry
|
||||
from clan_lib.api.tasks import WebThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from clan_app.api.middleware import Middleware
|
||||
from clan_app.middleware.base import Middleware
|
||||
|
||||
from .http_bridge import HttpBridge
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ import pytest
|
||||
from clan_lib.api import MethodRegistry, tasks
|
||||
from clan_lib.async_run import is_async_cancelled
|
||||
|
||||
from clan_app.api.middleware import (
|
||||
from clan_app.deps.http.http_server import HttpApiServer
|
||||
from clan_app.middleware import (
|
||||
ArgumentParsingMiddleware,
|
||||
MethodExecutionMiddleware,
|
||||
)
|
||||
from clan_app.deps.http.http_server import HttpApiServer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from ._webview_ffi import (
|
||||
from .webview_bridge import WebviewBridge
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from clan_app.api.middleware import Middleware
|
||||
from clan_app.middleware.base import Middleware
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from clan_lib.api import dataclass_to_dict
|
||||
@@ -9,6 +9,8 @@ from clan_lib.api.tasks import WebThread
|
||||
from clan_app.api.api_bridge import ApiBridge, BackendRequest, BackendResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from clan_app.middleware.base import Middleware
|
||||
|
||||
from .webview import Webview
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -19,7 +21,8 @@ class WebviewBridge(ApiBridge):
|
||||
"""Webview-specific implementation of the API bridge."""
|
||||
|
||||
webview: "Webview"
|
||||
threads: dict[str, WebThread] # Inherited from ApiBridge
|
||||
middleware_chain: tuple["Middleware", ...]
|
||||
threads: dict[str, WebThread] = field(default_factory=dict)
|
||||
|
||||
def send_api_response(self, response: BackendResponse) -> None:
|
||||
"""Send response back to the webview client."""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Middleware components for the webview API bridge."""
|
||||
"""Middleware components shared by API bridge implementations."""
|
||||
|
||||
from .argument_parsing import ArgumentParsingMiddleware
|
||||
from .base import Middleware, MiddlewareContext
|
||||
Reference in New Issue
Block a user