Assemblies and roles
A backup has roles: one server, many clients. An assembly says which machine takes which role, with refs:
{ yen, ... }:{ yen.host.nas.addr = "10.0.0.10"; yen.host.web.addr = "10.0.0.1"; yen.host.db.addr = "10.0.0.2"; yen.assembly.backup.roles = { # (1) server = [ yen.host.nas ]; # (2) client = [ yen.host.web yen.host.db ]; };}At (1), the assembly backup has roles. At (2), server lists one machine. client lists two.
Each machine spawns one member scope per (assembly, role) it takes part in. This kind file does it:
# An assembly is data: which machines take which role. Each machine spawns one `member` scope per role it has.{ yen, lib, ... }:let inherit (yen.lib) same is; membersOf = m: lib.listToAttrs ( lib.concatMap ( a: lib.concatMap ( role: lib.optional (lib.any (same m) a.roles.${role}) ( # (1) lib.nameValuePair "${a.name}.${role}" { assembly = a; # (2) inherit role; } ) ) (lib.attrNames a.roles) ) (lib.attrValues yen.assembly) );in{ yen.kinds.fleet = { spawns.host = _: yen.host; sinks = { out.reduce = yen.lib.reduce.merge; exports.reduce = yen.lib.reduce.elements; # (3) what members publish, with the scope that published it }; }; yen.kinds.assembly.fields.roles = yen.lib.attrs; # role name -> host refs yen.kinds.host = { fields.addr = yen.lib.str; spawns.member = membersOf; # (4) sinks.config = { reduce = yen.lib.reduce.merge; out = { host, config, ... }: { config.${host.name} = config; }; }; }; yen.kinds.member = { }; # its elements go up to its machine
yen.fleet.main = { }; yen.flake = { out, ... }: out;}At (1), for each assembly and each of its roles, the machine looks for itself in the list of that role.
At (2), the member holds a ref to its assembly. At (4), spawns.member creates the members. At (3), the sink exports
collects what members publish. An aspect at a member scope runs once per assembly and role. An aspect at a machine that
has a member runs once per machine. These are the scopes:
flowchart TD n6c4c773b6d["fleet main"] nbe39bbfa9b["host db"] n8252fa3265["member backup.client"] ncaf29b11ff["host nas"] n1d79234a62["member backup.server"] n0f3c4939ab["host web"] nab77973dae["member backup.client"] n6c4c773b6d --> nbe39bbfa9b nbe39bbfa9b --> n8252fa3265 n6c4c773b6d --> ncaf29b11ff ncaf29b11ff --> n1d79234a62 n6c4c773b6d --> n0f3c4939ab n0f3c4939ab --> nab77973dae
Members exchange data
Section titled “Members exchange data”Clients publish their address to a fleet sink with elements. The engine sets from, so a member can only publish
as itself. The server reads the elements and builds its allow list:
# The backup: an aspect per role, at member scopes. Clients export their address; the server reads them.{ yen, lib, ... }:let inherit (yen.lib) same; role = r: yen.lib.all [ yen.kinds.member (s: same s.assembly yen.assembly.backup && s.role == r) # (1) ]; nas.scopesOf = yen.host.nas; # a tree request: nas, with its data me.nearest = yen.kinds.host; # a tree request: the host this member sits onin{ yen.aspects.backup-client = { at = role "client"; exports = { host, ... }: host.addr; # (2) published by the member scope: the engine sets `from` cycle = { tree, ... }: { tree = [ nas me ]; config = [ { backup.target = (lib.head (tree.select nas)).addr; backup.name = (tree.select me).name; } ]; }; }; yen.aspects.backup-server = { at = role "server"; config = { exports, ... }: { backup.allow = lib.sort (a: b: a < b) ( map (e: e.value) (lib.filter (e: yen.lib.holds yen.kinds.member e.from) exports) ); }; }; # once per machine that takes part in any role of the assembly yen.aspects.backup-tools = { at = yen.lib.below (s: yen.lib.holds yen.kinds.member s && same s.assembly yen.assembly.backup); config.packages = [ "borgbackup" ]; };}At (1), an aspect is active only at members of the assembly backup with a given role.
At (2), the client publishes its address with the key exports. The result:
{ db = { backup = { name = "db"; target = "10.0.0.10"; }; packages = [ "borgbackup" ]; }; nas = { backup = { allow = [ "10.0.0.1" "10.0.0.2" ]; }; packages = [ "borgbackup" ]; }; web = { backup = { name = "web"; target = "10.0.0.10"; }; packages = [ "borgbackup" ]; };}