Service modules
✨ darkone.service.adguardhome
Section titled “✨ darkone.service.adguardhome”Full-configured AdGuard Home for local gateway / router.
- enable
boolEnable local adguardhome service
darkone.service.adguardhome.enable = false;✨ darkone.service.ai
Section titled “✨ darkone.service.ai”Local Artifical Intelligence (open-webui + ollama + llms).
- enable
boolEnable local AI service
darkone.service.ai.enable = false;✨ darkone.service.audio
Section titled “✨ darkone.service.audio”Audio services: alsa, pulse (not jack for the moment).
Automatically adds users to the audio group when enabled.
- enable
boolEnable sound system
darkone.service.audio.enable = false;✨ darkone.service.dnsmasq
Section titled “✨ darkone.service.dnsmasq”Pre-configured dnsmasq for local gateway / router.
- enable
boolEnable local dnsmasq service
darkone.service.dnsmasq.enable = false;✨ darkone.service.docs
Section titled “✨ darkone.service.docs”A full-configured LaSuite Docs module.
- enable
boolEnable local docs service - s3Host
strS3 backend hostname - s3Port
portS3 backend port - s3Bucket
strS3 bucket name for document storage
darkone.service.docs = { enable = false; s3Host = "127.0.0.1"; s3Port = dnfConfig.network.ports.garage; s3Bucket = "docs";};✨ darkone.service.element
Section titled “✨ darkone.service.element”Element web client for local matrix service.
- enable
boolEnable local element service
darkone.service.element.enable = false;✨ darkone.service.fail2ban
Section titled “✨ darkone.service.fail2ban”Fail2ban DNF specific module.
- enable
boolEnable fail2ban with DNF specificities
darkone.service.fail2ban.enable = false;✨ darkone.service.forgejo
Section titled “✨ darkone.service.forgejo”A full-configured forgejo git forge.
- enable
boolEnable local forgejo service
darkone.service.forgejo.enable = false;✨ darkone.service.garage
Section titled “✨ darkone.service.garage”A full-configured local Garage S3 service.
Provides an internal S3-compatible object storage backend accessible only on 127.0.0.1:3900.
- enable
boolEnable local Garage S3 service - srvPort
portS3 API port exposed on the internal IP - s3Region
strS3 region name (must match consumer config) - capacity
strNode storage capacity hint passed togarage layout assign. Used only at first boot for layout initialization; does not act as a hard quota on the underlying filesystem. Supported suffixes: B, KB, MB, GB, TB, PB.
darkone.service.garage = { enable = false; srvPort = srvPort; s3Region = s3Region; capacity = "500GB";};✨ darkone.service.geneweb
Section titled “✨ darkone.service.geneweb”GeneWeb — Powerful Genealogy Service.
- enable
boolEnable local GeneWeb genealogy service - enablePasswords
boolEnable sops passwords (not recommanded)
darkone.service.geneweb = { enable = false; enablePasswords = false;};✨ darkone.service.harmonia
Section titled “✨ darkone.service.harmonia”Harmonia: local Nix binary cache server (serves this host’s /nix/store).
Exposes locally built / realised store paths over plain HTTP (port 5000),
signed with the deployment-wide binary-cache key. Enable per host from
etc/config.yaml (services.harmonia), like any other DNF service.
- enable
boolEnable a local Harmonia Nix binary cache server
darkone.service.harmonia.enable = false;✨ darkone.service.headscale.audit
Section titled “✨ darkone.service.headscale.audit”DNF headscale: headscale-audit. Doc: ../headscale.nix header.
✨ darkone.service.headscale.dns
Section titled “✨ darkone.service.headscale.dns”DNF headscale: tailnet pivot DNS. Doc: ../headscale.nix header.
✨ darkone.service.headscale.enroll
Section titled “✨ darkone.service.headscale.enroll”DNF headscale: dnf-tailnet-enroll. Doc: ../headscale.nix header.
✨ darkone.service.headscale
Section titled “✨ darkone.service.headscale”A full-configured headscale service for HCS.
The tailnet ACL policy is generated from the topology (zones, hosts, users)
by dnfLib.mkHeadscalePolicy, validated by headscale policy check at build
time, then reloaded on change (SIGHUP) without restarting headscale.
- Machines are tagged:
tag:hcs,tag:gw-<zone>,tag:admin(host whosegroupscontainsadmin). - Machines and zone LANs reach each other. Personal devices: DNS and HTTPS
on the HCS, HTTPS on zone gateways (every zone for admins, else their
zone-*groups). SSH from admin stations andpolicy.adminDevices. - Every addressed zone host is a policy alias (
nas-> its LAN IP): any other service opens throughpolicy.extraAcls(nas:445). - Personal devices log in through Kanidm (OIDC), for members of the Kanidm
tailnetgroup; their keys expire afternodeExpiry.
darkone.service.headscale.policy.adminDevices.phone-alice = "100.64.0.9";Sub-modules (headscale/):
dns.nix: unbound, the tailnet pivot DNS, and itstailnet-machinesview;audit.nix:headscale-audit, live nodes against the declared topology;enroll.nix:dnf-tailnet-enroll, behindjust tailnet-enroll.
Pivot DNS (dns.nix)
Section titled “Pivot DNS (dns.nix)”unbound on the HCS. Internal names reach it through headscale split DNS; it forwards each zone domain to the zone gateway, the rest to Quad9 over TLS.
View tailnet-machines: tagged nodes (HCS aside) get the zone LAN address
of the global services a zone serves (git.<domain> on a gateway),
everyone else the public one. unbound-tailnet-view lists their tailnet
IPs from headscale, declared nowhere.
Audit (audit.nix)
Section titled “Audit (audit.nix)”headscale-audit, every 15 min: live nodes against the declared topology.
Metrics for the dnf-headscale-<zone> alerts, details in its journal. It
never tags nor deletes.
Enrollment (enroll.nix)
Section titled “Enrollment (enroll.nix)”dnf-tailnet-enroll, root side of just tailnet-enroll: single-use keys
tagged from the declared topology; the enrolled node gets its declared name
and tags.
- enable
boolEnable headscale DNF service - enableGRPC
boolOpen GRPC TCP port - nodeExpiry
strKey expiry of personal devices (0: never). Tagged machines never expire. - policy.enforce
boolDefault-deny tailnet ACLs.falseallows all traffic, for a migration only. - policy.adminDevices
attrsPersonal devices granted SSH on every machine and zone: name -> tailnet IPv4. - policy.exitNodeSources
listOf strPolicy sources allowed to use the HCS as exit node. - policy.extraHosts
attrsExtra policy host aliases (name -> CIDR), to use inextraAcls. Declared zone hosts already have one. - policy.extraAcls
listOf (attrsOf anything)ACL rules appended after the generated ones. - shared
rawHeadscale values shared with theheadscale/sub-modules.
darkone.service.headscale = { enable = false; enableGRPC = false; nodeExpiry = "180d"; policy.enforce = true; policy.adminDevices = { }; policy.exitNodeSources = [ ]; policy.extraHosts = { }; policy.extraAcls = [ ]; shared = { inherit policy auditSpec idmUrl hcsTailnetIpv4 ; };};✨ darkone.service.home-assistant
Section titled “✨ darkone.service.home-assistant”A Home Assistant with some plugins (wip).
- enable
boolEnable home assitant
darkone.service.home-assistant.enable = false;✨ darkone.service.homepage
Section titled “✨ darkone.service.homepage”Dynamically configured homepage dashboard for your local network.
- enable
boolEnable homepage dashboard + httpd + host - protect
boolRestrict homepage to authenticated Kanidm users (oauth2-proxy forward auth) - localServices
listOf attrsServices to add in Local Applications section - globalServices
listOf attrsFull network common & public-accessible services - remoteServices
listOf attrsServices to add in Remote Applications section - bookmarks
listOf attrsReplace default bookmarks (links) - widgets
listOf attrsReplace default widgets
darkone.service.homepage = { enable = false; protect = true; localServices = [ ]; globalServices = [ ]; remoteServices = [ ]; bookmarks = [ ]; widgets = [ ];};✨ darkone.service.idm.provision
Section titled “✨ darkone.service.idm.provision”DNF idm: Kanidm provisioning. Doc: ../idm.nix header.
- oauth2
attrsOAuth2/OIDC client templates contributed by service modules. Kanidm provisions one client per matching entry innetwork.services, withclientId = dnfLib.oauth2ClientName.- enable
boolWhether to provision OAuth2 clients for this template. - clientName
nullOr strOverride the kanidm client name. Defaults to dnfLib.oauth2ClientName. - displayName
strHuman-readable name shown on the kanidm consent screen. - imageFile
pathApplication icon. Re-uploaded on every kanidm-provision run. - redirectPaths
listOf strOAuth2 redirect paths (one per accepted callback URL). - landingPath
strAuto-connect entry point path on the service. - enableLegacyCrypto
boolAllow legacy JWT signing algorithms (eg. RS256). - allowInsecureClientDisablePkce
boolDisable PKCE on the client (only for clients that do not implement it). - preferShortUsername
nullOr boolUse the short username (no domain) in thepreferred_usernameclaim. - extra
attrsExtra attributes merged into the provisioned client (claimMaps, etc).
- enable
darkone.service.idm.provision = { oauth2.enable = true; oauth2.clientName = null; oauth2.displayName = null; oauth2.imageFile = null; oauth2.redirectPaths = [ ]; oauth2.landingPath = "/"; oauth2.enableLegacyCrypto = false; oauth2.allowInsecureClientDisablePkce = false; oauth2.preferShortUsername = null; oauth2.extra = { };};✨ darkone.service.idm.replication
Section titled “✨ darkone.service.idm.replication”DNF idm: Kanidm replication. Doc: ../idm.nix header.
- replication
rawReplication facts of this node.
darkone.service.idm.replication.replication = { inherit isMainReplica isReplConsumer; };✨ darkone.service.idm
Section titled “✨ darkone.service.idm”Kanidm (identity manager) DNF Service.
Sub-modules (idm/):
replication.nix: multi-zone read-only replication, HCS to zone gateways;provision.nix: OAuth2 clients (template registry), groups and persons.
Replication (replication.nix)
Section titled “Replication (replication.nix)”Automatic, derived from where idm is declared:
- idm on the HCS only, or on a gateway without HCS: one instance;
- on the HCS and >= 1 zone gateway: the HCS supplies (WriteReplica), each idm gateway consumes.
Two-step bootstrap: just apply generates every replication certificate
(gateways stay WriteReplicaNoUI), then just idm-sync-certs + just apply
adds the partners and flips the gateways to ReadOnlyReplica.
Provisioning (provision.nix)
Section titled “Provisioning (provision.nix)”Every OIDC-capable service module contributes a client template to
darkone.service.idm.oauth2; one client is provisioned per (template,
instance), merged by clientId across zones. Never on a replication
consumer: it mirrors the HCS.
- enable
boolEnable local SSO with Kanidm
darkone.service.idm.enable = false;✨ darkone.service.immich
Section titled “✨ darkone.service.immich”Immich (photo management) full-configured service.
- enable
boolEnable local immich service - enableMachineLearning
boolEnable machine learning features (face recognition, object detection) - enableRedis
boolEnable Redis for caching (recommended for performance)
darkone.service.immich = { enable = false; enableMachineLearning = false; enableRedis = false;};✨ darkone.service.jellyfin
Section titled “✨ darkone.service.jellyfin”A full-configured jellyfin server.
- enable
boolEnable jellyfin service
darkone.service.jellyfin.enable = false;✨ darkone.service.jitsi-meet
Section titled “✨ darkone.service.jitsi-meet”A full-configured jitsi-meet service.
- enable
boolEnable local jitsi-meet service
darkone.service.jitsi-meet.enable = false;✨ darkone.service.loki
Section titled “✨ darkone.service.loki”Loki + Alloy, http stats with grafana.
- enable
boolDeploys the Loki server + Grafana datasource (colocated with Grafana). - isClient
boolDeploys Alloy to collect local Caddy access logs. - retentionTime
strLog retention duration in Loki (30 days by default).
darkone.service.loki = { enable = config.darkone.service.monitoring.enable; isClient = config.services.caddy.enable && hasMonitoringHost; retentionTime = "720h";};✨ darkone.service.matrix.bridges
Section titled “✨ darkone.service.matrix.bridges”DNF matrix: mautrix bridges. Doc: ../matrix.nix header.
✨ darkone.service.matrix.mas
Section titled “✨ darkone.service.matrix.mas”DNF matrix: authentication delegated to MAS. Doc: ../matrix.nix header.
✨ darkone.service.matrix.rtc
Section titled “✨ darkone.service.matrix.rtc”DNF matrix: MatrixRTC calls (LiveKit). Doc: ../matrix.nix header.
✨ darkone.service.matrix-admin
Section titled “✨ darkone.service.matrix-admin”Matrix administration UI (Ketesa, formerly synapse-admin) for the local server.
Static SPA, no backend: the browser talks straight to synapse’s admin API and
to MAS’s, so the whole service is a Caddy file_server over a store path.
The DNF service name is deliberately not the package name. The UI is
swappable (Element Admin, a later fork) without touching the URL, the DNS
record or etc/config.yaml.
Reachable from the LAN and the tailnet only (proxy.isInternal): the bundle
holds no secret, but an admin login page has no reason to face the Internet.
- enable
boolEnable the local matrix administration UI
darkone.service.matrix-admin.enable = false;✨ darkone.service.matrix
Section titled “✨ darkone.service.matrix”DNF matrix (synapse) homeserver.
Sub-modules (matrix/):
mas.nix: authentication, delegated to Matrix Authentication Service;bridges.nix: mautrix bridges, usable by every local account;rtc.nix: MatrixRTC backend (LiveKit) for Element Call.
Federation
Section titled “Federation”Federation is configurable via darkone.service.matrix.federation:
enable = false: blocks all federation (empty domain whitelist).enable = true; whitelist = [ ]: open federation with every server.enable = true; whitelist = [ "ami.org" ]: allowlist (inbound + outbound).
Discovery stays locked regardless (rooms absent from remote directories, profiles private over federation), so the network is reachable but not searchable.
Friend self-registration (friendRegistration.enable) opens token-gated
local password accounts alongside Kanidm OIDC users. Mint a token on the host
with sudo dnf-mas manage issue-user-registration-token.
Administrators
Section titled “Administrators”network.matrix.admins (local parts) is the single declarative source for
both administrations, applied at rebuild with no database write:
- bridges:
adminlevel in every mautrixpermissionsmap; - server:
policy.data.admin_users, which the bundled MAS policy turns into theurn:mas:adminandurn:synapse:admin:*scopes (MAS admin API, synapse admin API,matrix-adminUI login).
Authentication (mas.nix)
Section titled “Authentication (mas.nix)”All authentication goes through Matrix Authentication Service, always:
Element X, QR login and the /account self-service portal only exist there,
and synapse’s own auth is deprecated upstream (MSC3861). Kanidm stays the
identity source: MAS is the OIDC client instead of synapse, and synapse only
asks MAS to introspect tokens.
Same vhost: MAS owns the root + compat auth endpoints, synapse keeps
/_matrix/* and /_synapse/*. Client discovery is automatic (synapse
serves auth_metadata itself), no well-known change.
Required sops secrets, all created by just configure-admin-host:
mas-encryption-secret, mas-synapse-secret and mas-rsa-private-key.
dnf-mas (root) wraps mas-cli for host-side administration: registration
tokens, promote-admin, register-user, syn2mas. MAS runs with
DynamicUser and reads its secrets from systemd credentials, absent outside
the running unit; the wrapper rebuilds an equivalent credentials directory
from the sops files, so it works whether the service is up or down (syn2mas
needs it down).
mas-cli only acts, never lists: enable the matrix-admin service for the
read side (accounts, devices, sessions), it speaks both admin APIs.
Bridges (bridges.nix)
Section titled “Bridges (bridges.nix)”mautrix bridges: WhatsApp, Signal, Telegram, Messenger, Discord. Each user
links their own remote account by talking to the bridge bot
(@whatsappbot, @signalbot, … then login); sessions are isolated per
user. Bridge administrators: cf. “Administrators” above.
Double puppeting: official appservice method
(https://docs.mau.fi/bridges/general/double-puppeting.html 🡕). A shared
doublepuppet appservice token lets bridges send remote-originated messages
as the user’s real matrix account. Required sops secrets:
mautrix-doublepuppet-as-token and mautrix-doublepuppet-hs-token
(generated by just configure-admin-host).
Every bridge’s appservice as/hs tokens are sops-provided, never auto-generated: the registration file is a pure function of the secrets, so resetting a bridge’s state dir never invalidates the registration synapse loaded (“as_token was not accepted”, registration wipe + synapse restart).
Calls (rtc.nix)
Section titled “Calls (rtc.nix)”Two stacks coexist, because no single one covers every client:
- Legacy 1:1 WebRTC, negotiated over synapse and relayed by coturn
(
darkone.service.turn). The only thing Element Classic speaks. - MatrixRTC (
matrixRtc.enable): a LiveKit SFU plus its authorization service, for group calls and for Element Call. Element X speaks only this one and reports “call is not supported” (MISSING_MATRIX_RTC_TRANSPORT) without it; Element Web/Desktop embed Element Call too and gain group calls from it.
Both are served from the matrix vhost, next to synapse and MAS: the SFU
websocket on /livekit/sfu, the JWT service on /livekit/jwt. Clients
discover it from matrix_rtc.transports (synapse, MSC4143) and, as a
fallback for older ones, from rtc_foci in the well-known.
Required sops secret: livekit-secret (just configure-admin-host),
shared by the SFU and the JWT service.
- enable
boolEnable matrix (synapse) service - federation.enable
boolAllow server-to-server federation. False blocks all federation. - federation.whitelist
listOf strEmpty = federate with all servers; non-empty = only these domains (inbound + outbound). - matrixRtc.enable
boolEnable the MatrixRTC backend (LiveKit SFU) for Element Call group calls. - friendRegistration.enable
boolAllow friends to self-register with an invite token (token-gated). - bridges.whatsapp.enable
boolMautrix WhatsApp bridge (login by QR code). - bridges.signal.enable
boolMautrix Signal bridge (login by QR code). - bridges.telegram.enable
boolMautrix Telegram bridge (login by phone number). - bridges.messenger.enable
boolMautrix Facebook Messenger bridge (login by cookies). - bridges.discord.enable
boolMautrix Discord bridge (login by QR code, experimental). - shared
rawMatrix values shared with thematrix/sub-modules.
darkone.service.matrix = { enable = false; federation.enable = false; federation.whitelist = [ ]; matrixRtc.enable = false; friendRegistration.enable = false; bridges.whatsapp.enable = true; bridges.signal.enable = true; bridges.telegram.enable = true; bridges.messenger.enable = true; bridges.discord.enable = false; shared = { inherit params masKanidmUlid livekitJwtUrl matrixAdmins ; };};✨ darkone.service.mealie
Section titled “✨ darkone.service.mealie”Mealie Recipe Management
- enable
boolEnable mealie service
darkone.service.mealie.enable = false;✨ darkone.service.minio
Section titled “✨ darkone.service.minio”A full-configured local MinIO S3 service. (wip)
Provides an internal S3-compatible object storage backend accessible only on 127.0.0.1:9000. The console web UI runs on 127.0.0.1:9001 for administration and debugging.
- enable
boolEnable local MinIO S3 service
darkone.service.minio.enable = false;✨ darkone.service.monitoring.fs-readonly
Section titled “✨ darkone.service.monitoring.fs-readonly”DNF monitoring: rw mounts the kernel turned read-only. Doc: ../monitoring.nix header.
✨ darkone.service.monitoring
Section titled “✨ darkone.service.monitoring”Supervision module: Grafana dashboards + per-node exporter.
Read-only watch (monitoring/fs-readonly.nix): every node exports
dnf_filesystem_forced_readonly, its rw mounts whose filesystem the kernel
turned read-only, behind FilesystemReadOnly. Intended ro mounts and binds
never appear; the file lives on tmpfs, so it outlives the failing disk.
- enable
boolEnable monitoring with prometheus, grafana and node exporter - isNode
boolIs a monitoring node - kioskTarget
strTarget (relative, no/) for the automatic redirect to Grafana from the monitoring domain root. Overridden by the Loki module when active to point to a multi-source home dashboard.
darkone.service.monitoring = { enable = false; isNode = lib.hasAttrByPath [ "features" "monitoring-node" ] host; kioskTarget = "d/dnf-monitoring-home/home?kiosk";};✨ darkone.service.nextcloud
Section titled “✨ darkone.service.nextcloud”Nextcloud full-configured service.
-
enable
boolEnable local nextcloud service -
adminUser
strAdmin username for Nextcloud -
enableSsoRedirect
boolSend users straight to Kanidm instead of showing the Nextcloud login form (user_oidcallow_multiple_user_backends). Saves one click on every login, including the desktop client’s Login Flow v2.No effect when Kanidm is absent from the network.
-
plugins
listOf (enum (appstoreApps ++ shippedToggleableApps))Nextcloud apps to enable. Onlycalendar,contactsandtasksare on by default; Talk (spreed), the dashboard, activity feed, photos, and every other app stay disabled until listed here.user_oidc(Kanidm SSO) andnotify_push(client push) are always on, regardless of this list.
darkone.service.nextcloud = { enable = false; adminUser = "admin"; enableSsoRedirect = true; plugins = [ ];};✨ darkone.service.nfs
Section titled “✨ darkone.service.nfs”NFS server + client for home shares.
- enable
boolEnable NFS DNF server (avoid enable manually) - serverDomain
strNFS Server FQDN
darkone.service.nfs = { enable = hasServer && (isServer || isClient); serverDomain = "nfs";};✨ darkone.service.nix-cache
Section titled “✨ darkone.service.nix-cache”Per-zone Nix binary-cache proxy (nginx proxy_cache).
Caches the public Nix cache (cache.nixos.org) on the zone gateway so a
fleet-wide deploy pulls each closure from the WAN once, then serves it over
the LAN to every other host. Locally built paths are served straight from
harmonia (see harmonia.nix); this proxy never fronts harmonia.
- enable
boolthe per-zone Nix binary-cache proxy (nginx in front of cache.nixos.org) - maxSize
strMaximum on-disk cache size; nginx evicts least-recently-used entries beyond it. - roaming
boolNomadic host (laptop moved between zones): replace the zone-pinned substituters with the zone-neutral names every zone’s DNS resolves to its own cache services. Outside any DNF zone the names NXDOMAIN instantly and Nix falls back to cache.nixos.org. Declared by theroamingfeature inconfig.yaml; set it by hand for a temporary move.
darkone.service.nix-cache = { enable = false; maxSize = "100g"; roaming = (host.features or { }) ? "roaming";};✨ darkone.service.openclaw
Section titled “✨ darkone.service.openclaw”OpenClaw assistant of an AI space (gateway + control UI, behind the zone SSO).
One instance per ai-space host, declared like any service:
- hostname: "ia-pro" profile: "ai-space" users: ["alice"] # members: Kanidm group `space-ia-pro` features: ["monitoring-node"] # alerts: space down, gateway unit failed services: { openclaw: {} }- access: Caddy forward auth, members of
space-<host>only (proxy.spaceMembersOnly); OpenClaw trusts the zone gateway as proxy (trusted-proxy) and reads the identity fromx-auth-request-email; - devices: a member’s browser is paired on first visit, without operator admin (granted from the host CLI only);
- port: reachable from the zone gateway only;
- host CLI:
openclaw(alone: the TUI), re-run as the service user, pasttrusted-proxywith the gateway password (openclaw-gateway-password,just space-init); admins andcliUsers(default: the owner) only; - state:
/var/lib/openclaw(provider logins, sessions, workspace, Matrix E2EE keys); - owner (
owner, default: the only member): its subscription runs the agent, it gets approval requests and proactive messages; - Matrix: DM channel with the bot
@<host>:<domain>, members only, rooms ignored, one session per member when several, when the network has Matrix andjust space-init <host>created the bot (markervar/generated/spaces/<host>.nix, token in the space’s sops file); exec approvals asked to the owner in DM; - agent tooling: MCP, skills, Claude Code / Codex / OpenCode and
packagesof the space (darkone.host.ai-space), shared with the members’ homes; - identity (
identity: name, emoji, avatar): declared, the config being read-only (openclaw agents set-identityfails); mutableConfig: config writable from the UI and the CLI, Nix keys merged at each start (imposed: access, channels, plugins, store paths; seeded once: model, exec, identity…), owner getsoperator.admin;- workspace (
/var/lib/openclaw/workspace):workspacefiles (AGENTS.md, SOUL.md…) replaced at each start, others left to the agent; - projects: the space’s shared
projectsDiris the agent’s working dir (agents.defaults.cwd),projectsgit-cloned once into it; - coding agents (Claude Code, Codex, OpenCode of the space): background
workers (
coding-agentskill), chat-bound sessions (/acp spawn, acpx); - Talk (
talk): live voice conversation in the browser, OpenAI GPT-Live, work delegated to the agent (agent-consult), ChatGPT login first; - workflows: Lobster pipelines (
lobster,.lobsterfiles throughworkspace), declared automations (automations, kept in sync, others left to the agent and the UI); - voice notes: whisper.cpp (
transcription); on a microVM guest the hypervisor’s server (large-v3-turbo), local ggmlsmallas fallback; local only on bare metal.
darkone.service.openclaw = { workspace."AGENTS.md" = ./agents/AGENTS.md; projects.notes = "git@forge.example.com:alice/notes.git";};Models: model + extraModels (/model in the chat), the owner’s
subscriptions first, else an API key (apiKeys, sops openclaw/<VAR>);
providers adds OpenAI-compatible endpoints (Nous Portal…).
-
enable
boolEnable the OpenClaw assistant of an AI space -
owner
nullOr strMember owning the space: its subscription logs the agent in (model), it receives approval requests and proactive messages. Required with several members (usersorder is not kept). -
cliUsers
listOf strMembers running the host CLI (openclaw) as the service user, by a sudo rule: TUI, provider logins, admin. Its!shell commands run asopenclawtoo, the space’s tokens within reach. -
model
strMain model,<provider>/<model>: through the owner’s subscription when its provider is insubscriptions, else an API key (apiKeys). -
extraModels
listOf strOther models of the catalog, picked in the chat with/model; same rules asmodel. -
subscriptions
listOf ( enum [ "anthropic" "openai" ] )Subscriptions of the owner, used first by the models of their provider:anthropic: Claude Pro/Max through theclaudeCLI (needsdarkone.host.ai-space.enableClaude);openai: ChatGPT Plus/Pro through thecodexplugin.
Other providers, or one left out, go through an API key (
apiKeys). -
apiKeys
listOf (strMatching "[A-Z][A-Z0-9_]*")Provider API keys, by variable: read from the space’s sops file (openclaw/<VAR>,just space-sops <host>) into the gateway environment.ANTHROPIC_API_KEYis dropped beside theanthropicsubscription, which it would override. -
providers
attrsProviders absent from OpenClaw (Nous Portal…), OpenAI-compatible by default.- baseUrl
strAPI endpoint. - api
strAPI flavour (OpenClawmodels.providers.<id>.api). - apiKey
nullOr (strMatching "[A-Z][A-Z0-9_]*")Key variable, read likeapiKeys. - models
listOf strModel ids, referenced as<provider>/<id>.
- baseUrl
-
mutableConfig
boolConfig writable from the UI andopenclaw config set, the owner being operator admin. Nix keys merged at each start:- imposed, rewritten (also on any change of the file): gateway and access, Matrix channel, plugins, MCP servers, store paths;
- seeded, written when absent, then the UI rules:
model,exec,identity, talk, ACP, skill switches.
-
exec
enum [ "full" "auto" "ask" "allowlist" "deny" ]Command policy of the agent (OpenClawtools.exec.mode):full: any command, unapproved (OpenClaw default);ask: allowlisted commands run, any other asks the owner;auto: a model reviews misses, the owner decides when it is unsure;allowlist,deny: allowlisted commands only, none.
Allowlist:
openclaw approvals allowlist add --agent main <path>. With theclaude-cliruntime, pipes, redirections and substitutions are refused before any prompt outsidefull. -
identity.name
nullOr strName of the assistant, shown in the UI. -
identity.emoji
nullOr strEmoji of the assistant, shown in the UI. -
identity.avatar
nullOr strAvatar image (URL or workspace-relative path). -
workspace
attrsFiles of the agent workspace, by relative path: content or file. Replaced at each gateway start; undeclared files belong to the agent. -
projects
attrsGit repositories cloned once into<projectsDir>/<name>(darkone.host.ai-space.projectsDir), never pulled. SSH remotes use the key ofopenclaw(/var/lib/openclaw/.ssh, created by hand); the server key is accepted on first clone. -
talk.enable
boolTalk: live voice conversation in the browser (OpenAI GPT-Live) -
talk.model
strRealtime model.gpt-live-1-codex: the owner’s ChatGPT login (openclaw models auth login --provider openai), else a Platform key (OPENAI_API_KEY);gpt-live-1,gpt-realtime-2.1(camera): Platform key only. Pinned: unset, a Platform key wins over ChatGPT. -
talk.voice
strVoice of the model’s route (gpt-live-1-codex:cove,arbor,breeze,ember,juniper,maple,sol,spruce,vale). -
talk.instructions
nullOr linesInstructions appended to OpenClaw’s realtime prompt. -
lobster
boolLobster workflows: pipelines run by the gateway, with approval steps and resume, plusllm-task(structured LLM step). Workflow files go throughworkspace("workflows/<name>.lobster"). -
automations
attrsScheduled agent runs declared in Nix, kept in sync at each start (nix-<name>, edits from the UI overwritten); those of the agent and the UI are left alone.- schedule
strCron expression (5 or 6 fields), orevery <duration>(every 2h). - message
linesPrompt of the agent’s run. - announce
boolSend the final reply to the chat (owner’s last channel). - model
nullOr strModel override,<provider>/<model>or alias. - enable
boolFalse: kept disabled; once so, re-enabled from the UI only (openclaw automations enable <id>).
- schedule
-
transcription.enable
boolTranscribe voice notes (whisper.cpp, CPU). -
transcription.backend
enum [ "hypervisor" "local" ]Where voice notes are transcribed:hypervisor: the hypervisor’s whisper.cpp server (bigger model, more cores),modelas fallback;local: on this host, withmodel.
-
transcription.model
pathwhisper.cpp ggml model of local transcription (localbackend, fallback). -
transcription.language
strSpoken language (auto: detected per note).
darkone.service.openclaw = { enable = false; owner = "alice"; cliUsers = [ ]; model = "openai/gpt-6-astra"; extraModels = [ ]; subscriptions = [ ]; apiKeys = [ ]; providers.baseUrl = "https://inference-api.nousresearch.com/v1"; providers.api = "openai-completions"; providers.apiKey = "NOUS_API_KEY"; providers.models = [ ]; mutableConfig = false; exec = "ask"; identity.name = "Jarvis"; identity.emoji = "🦉"; identity.avatar = null; workspace = { }; projects = { }; talk.enable = false; talk.model = "gpt-live-1-codex"; talk.voice = "sol"; talk.instructions = "Réponds en français, en phrases courtes."; lobster = false; automations.schedule = "0 7 * * 1-5"; automations.message = ""; automations.announce = true; automations.model = null; automations.enable = true; transcription.enable = true; transcription.backend = if config.darkone.system.microvm.hypervisor != null then "hypervisor" else "local"; transcription.model = pkgs.fetchurl { url = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin"; sha256 = "0ywqxbziyp2bv72riyjpw4brk9v46d4cfbjfwqvvjrrq0srakqqv"; }; transcription.language = "fr";};✨ darkone.service.outline
Section titled “✨ darkone.service.outline”A full-configured outline wiki.
- enable
boolEnable local outline service
darkone.service.outline.enable = false;✨ darkone.service.oxicloud
Section titled “✨ darkone.service.oxicloud”OxiCloud — Fast Sovereign Cloud (file storage, WebDAV, CalDAV & CardDAV).
- enable
boolEnable local OxiCloud service
darkone.service.oxicloud.enable = false;✨ darkone.service.postfix
Section titled “✨ darkone.service.postfix”Postfix SMTP Relay.
- enable
boolEnable Postfix SMTP Relay
darkone.service.postfix.enable = false;✨ darkone.service.printing
Section titled “✨ darkone.service.printing”Printers and scanners.
- enable
boolDefault useful packages - loadAll
boolFull printers and scanners - enableScanners
boolEnable scanners - enableHpPrinters
boolHP printers only - enableManualInstall
boolManual drivers installation
darkone.service.printing = { enable = false; loadAll = false; enableScanners = false; enableHpPrinters = false; enableManualInstall = false;};✨ darkone.service.prometheus
Section titled “✨ darkone.service.prometheus”Prometheus monitoring server with declarative Alertmanager escalation.
-
enable
boolEnable the Prometheus metrics + alerting server -
retentionTime
strPrometheus metrics retention duration -
alerting.enable
boolEnable Alertmanager (defaults on once the Matrix alert rooms are provisioned) -
alerting.matrix.enable
boolDeliver alerts to Matrix rooms via the matrix-alertmanager bot -
alerting.matrix.userId
strMatrix user ID of the alert bot (defaults from network.matrix.bot) -
alerting.matrix.warningsRoom
strMatrix room ID for warnings (defaults from network.matrix.warningsRoom) -
alerting.matrix.incidentsRoom
strMatrix room ID for incidents (defaults from network.matrix.incidentsRoom) -
alerting.email.enable
boolAlso send critical alerts by mail (through the local Postfix relay) -
alerting.email.to
strRecipient of critical alert mails -
alerting.network.enable
boolProbe network reachability (gateway/tailnet/DNS) with blackbox_exporter -
alerting.network.internetProbeTargets
listOf strExternal IPs the zone gateway pings to detect a WAN outage. When all fail,ZoneInternetDownfires and inhibits the (false) down-alerts of hosts reachable only across the WAN (e.g. the HCS). Empty disables it. -
alerting.network.httpProbeUrls
listOf strHTTP(S) URLs probed for liveness (ServiceEndpointDown) and TLS certificate expiry (CertificateExpiringSoon/Critical). Empty by default: list the public web endpoints worth watching. Redirects to an SSO login (3xx) count as up; cert expiry is read from the TLS handshake regardless of HTTP status. -
alerting.silenceOnRebuild
boolShip the dnf-maintenance flag (textfile collector) so rebuilds inhibit a node’s alerts -
alerting.thresholds
attrsOverride resource alert thresholds (see dnf/lib/alerts.nix defaults) -
alerting.silences
listOf submoduleKnown, accepted alerts that must never notify. Each entry becomes an Alertmanager route to thenullreceiver: the alert still fires and stays visible in the Prometheus/Alertmanager/Grafana UIs, it just stops reaching Matrix and mail.Declared on the host running Prometheus, since that is where the zone’s rules and routes are generated. A fleet-wide consumer module (
usr/modules/alerts.nix) is the practical place: it is imported everywhere and only takes effect where Prometheus runs.- alert
strAlert name to mute (alertnamelabel). Always constrained, so sibling severities stay armed. - host
nullOr strRestrict the silence to one host. Null mutes the alert fleet-wide. - matchers
attrsExtra exact label matchers, ANDed with the others (e.g. the mount or device the alert is about). - reason
strWhy this alert is accepted, and what would make the silence removable.
- alert
darkone.service.prometheus = { enable = false; retentionTime = "30d"; alerting.enable = roomsReady; alerting.matrix.enable = true; alerting.matrix.userId = "@alertbot:domain.tld"; alerting.matrix.warningsRoom = "!warnings:domain.tld"; alerting.matrix.incidentsRoom = "!incidents:domain.tld"; alerting.email.enable = true; alerting.email.to = "admin@${network.domain}"; alerting.network.enable = true; alerting.network.internetProbeTargets = [ ]; alerting.network.httpProbeUrls = [ ]; alerting.silenceOnRebuild = true; alerting.thresholds = { }; alerting.silences = [ ]; alerting.silences.alert = "DiskSpaceLow"; alerting.silences.host = "srv-main"; alerting.silences.matchers = { }; alerting.silences.reason = null;};✨ darkone.service.restic.metrics
Section titled “✨ darkone.service.restic.metrics”DNF restic: backup freshness metrics. Doc: ../restic.nix header.
✨ darkone.service.restic.server
Section titled “✨ darkone.service.restic.server”DNF restic: the REST server. Doc: ../restic.nix header.
✨ darkone.service.restic
Section titled “✨ darkone.service.restic”Restic backup module: REST server + per-host backup targets.
Example (machine config):
darkone.service.restic = { enable = true; targets = [ { name = "main"; root = "rest:http://restic.my-zone.my-domain.tld:8888"; zone = "ag"; categories = [ "system" "nfs" ]; } ];};Repository layout per target: one repo per category, named after it.
<root>/<hostname>/system <- "system" category (/ minus excludes)<root>/<hostname>/nfs <- "nfs" category (/srv/nfs/<...>)<root>/<hostname>/medias <- "medias" category (/srv/medias/<...>)Sub-modules (restic/):
server.nix: the REST server, one account per fleet host;metrics.nix: backup freshness metrics for the restic alerts.
REST server (server.nix)
Section titled “REST server (server.nix)”One account per fleet host (restic/<hostname>/rest-password), checked
against an htpasswd assembled at boot; privateRepos confines each host to
its own <hostname>/ prefix.
Metrics (metrics.nix)
Section titled “Metrics (metrics.nix)”Monitored nodes only (monitoring-node feature): each job stamps its last
success in the textfile collector, and restic-declared lists the declared
jobs so a job that never succeeds still ages into a dnf-restic-<zone>
alert.
- enable
boolEnable restic backup client - enableDryRun
boolDry Run mode - enableWaitRemoteFs
boolRun backups only after remote-fs.target - enableServer
boolEnable restic REST server - listenAll
boolBind the REST server on 0.0.0.0 (off-site clients) - serverDataDir
strLocal storage root of the REST server (all hosts’ repos) - targets
listOf submoduleBackup destinations for this host (local path or REST URL)- name
strTarget id, used in backup/unit names (<category>-<name>) - root
strRepository root: local path or REST URL - zone
strZone selecting the repo passphrase (restic-password-<zone>) - categories
listOf ( enum [ "system" "nfs" "medias" ] )What to back up to this target
- name
- nfsPaths
listOf strNFS dirs (/srv/nfs/<xxx>) included in the ‘nfs’ category - mediasPaths
listOf strMedias dirs (/srv/medias/<xxx>) included in the ‘medias’ category - shared
rawRestic values shared with therestic/sub-modules.
darkone.service.restic = { enable = false; enableDryRun = false; enableWaitRemoteFs = false; enableServer = false; listenAll = false; serverDataDir = "/mnt/backup/restic"; targets = [ ]; targets.name = "main"; targets.root = "rest:http://restic.${zone.domain}:${toString srvPort}"; targets.zone = zone.name; targets.categories = [ ]; nfsPaths = [ ]; mediasPaths = [ ]; shared = { inherit params; };};✨ darkone.service.searx
Section titled “✨ darkone.service.searx”A full-configured hardened search engine.
- enable
boolEnable local search proxy
darkone.service.searx.enable = false;✨ darkone.service.stk
Section titled “✨ darkone.service.stk”SuperTuxKart shared tracks, served over NFS.
- enable
boolExport the shared SuperTuxKart tracks (avoid enable manually) - enableClient
boolMount the shared SuperTuxKart tracks of the zone - serverName
nullOr strHostname serving the tracks, default is the zone’sstkservice host
darkone.service.stk = { enable = stk.isServer; enableClient = false; serverName = stk.server;};✨ darkone.service.tailscale.autopause
Section titled “✨ darkone.service.tailscale.autopause”DNF tailscale: roaming auto-pause. Doc: ../tailscale.nix header.
✨ darkone.service.tailscale.cert-sync
Section titled “✨ darkone.service.tailscale.cert-sync”DNF tailscale: HCS certificate sync. Doc: ../tailscale.nix header.
✨ darkone.service.tailscale.selfheal
Section titled “✨ darkone.service.tailscale.selfheal”DNF tailscale: self-heal watchdog. Doc: ../tailscale.nix header.
✨ darkone.service.tailscale
Section titled “✨ darkone.service.tailscale”Tailscale client of the DNF tailnet (headscale on the HCS).
Joins the HCS, zone gateways (isGateway: zone subnet advertised, routes
accepted, own resolver kept) and roaming hosts.
Sub-modules (tailscale/):
cert-sync.nix: zone gateways pull the HCS Caddy certificates;selfheal.nix: watchdog restarting a disconnected tailscaled;autopause.nix: roaming clients pause tailscale on a zone LAN.
Certificate sync (cert-sync.nix)
Section titled “Certificate sync (cert-sync.nix)”Public certificates are issued on the HCS only; each zone gateway pulls the
whole Caddy storage over the tailnet (rsync as nix, elevated to caddy on
the HCS side), then publishes it to its own Caddy.
Self-heal (selfheal.nix)
Section titled “Self-heal (selfheal.nix)”Every minute, checks that the backend runs and the control plane sees the
node online; restarts tailscaled after a sustained loss. Exports
dnf_tailscale_* metrics for the TailscaleFlapping/TailscaleUnhealthy
alerts.
Auto-pause (autopause.nix)
Section titled “Auto-pause (autopause.nix)”Roaming clients only. On a DNF zone LAN, --accept-dns would hijack
resolv.conf (local dnsmasq/AdGuard) and --accept-routes collide with the
connected zone subnet: a NetworkManager dispatcher pauses tailscale there
and resumes it elsewhere.
- enable
boolEnable tailscale client to connect HCS - isGateway
boolThis tailscale node is a subnet gateway - isExitNode
boolConfigure this client as exit node - selfHeal.enable
boolWatchdog: detect headscale disconnection and restart tailscaled. - autoPauseOnLan.enable
boolRoaming client: pause tailscale (down) while plugged into a known DNF zone LAN, resume it elsewhere. Non-gateway NetworkManager clients only. - shared
rawTailscale values shared with thetailscale/sub-modules.
darkone.service.tailscale = { enable = false; isGateway = false; isExitNode = false; selfHeal.enable = true; autoPauseOnLan.enable = false; shared = { inherit tsBin isHcsSubnetGateway selfHealEnable selfHealStateDir autoPauseEnable autoPauseStateFile ; };};✨ darkone.service.turn
Section titled “✨ darkone.service.turn”Coturn server (matrix).
Add DNS entries to optimize :
Type,Name,Priority,Pds,Port,TargetSRV,_stun._udp,0,0,3478,turn.mydomain.tldSRV,_stun._tcp,0,0,3478,turn.mydomain.tldSRV,_turn._udp,0,0,3478,turn.mydomain.tldSRV,_turn._tcp,0,0,3478,turn.mydomain.tldSRV,_turns._tcp,0,0,5349,turn.mydomain.tld- enable
boolEnable local turn service (visio)
darkone.service.turn.enable = false;✨ darkone.service.vaultwarden
Section titled “✨ darkone.service.vaultwarden”A full-configured vaultwarden server.
- enable
boolEnable local Vaultwarden service - enableSmtp
boolEnable SMTP to send emails (recommended, needsnetwork.smtp)
darkone.service.vaultwarden = { enable = false; enableSmtp = true;};