troupe
Turnkey XMPP-ready host bootstrap for the clown federation. troupe aims to be
an extremely easy home-manager and NixOS module that stands a host up as an
XMPP federation peer — prosody, headscale tailnet join, and DNS resolution — so
that clown can use XMPP for inter- and
intra-host messaging (the substrate for clown's troupe binary and RFC-0015's
reserved XMPP transport).
The name is deliberate: troupe is the upstream home the clown troupe messaging
concept converges to.
Status — v0.1
Landed:
homeManagerModules.prosody— a per-user (systemd.user) Prosody XMPP node. Generatesprosody.cfg.lua, runs the daemon foreground, carries its own lego (DNS-01) cert lifecycle, and flips between a loopback bootstrap posture and a federated posture that binds only to specific tailnet address(es) — resolved at service-start viafederatedInterfacesCommand(for a join-time dynamic headscale IP) or a staticfederatedInterfaceslist. Extracted from eng'shome/prosody.nix.packages.parley+ themoxins/parleymoxin — a stateless XMPP MUC send/read client (slixmpp) for cross-session coordination over the backbone.packages.troupe+ thego-pkgs/go-pkgs-testoutputs — the messaging binary (clown RFC-0015 surface:send/read/list/message+ the troupe MCP surface), packaged via igloo's go-pkgs protocol. troupe is both a Go producer (its own go-pkgs) and a consumer: its messaging backend is ringmaster'sjobwake/jobmcpsubstrate, bridged viagoFlakeInputs(a ringmaster bump is aflake.lock-only edit, nothing fetched over the network). The CLI is transport-abstract (RFC-0015 §4): the defaultlocalbackend is the ringmaster journal; the opt-inxmppbackend (TROUPE_TRANSPORT=xmpp, RFC-0001) carries chat over Prosody MUC rooms via a per-sessiontroupe agentdaemon.
Roadmap
troupe's charter is the whole XMPP-ready host, delivered in phases:
- v0.2 — DNS client-resolver bootstrap. The client side of resolution so a
joined host resolves the federation's XMPP names (quad100-pin /
--accept-dns/ systemd-resolved). The authoritative server-side DNS stays with the hub. - Deferred — headscale-join module. troupe as the single reusable home for the
client tailnet join: a NixOS module + a non-NixOS reconciler (today eng runs
bin/bootstrap-twerk-headscale.bash; the NixOS form lives in circus). Needs coordination with the circus (server/DNS) owner before it moves. - NixOS module surface alongside the home-manager modules, and a
services.troupe.enable-style umbrella that turns the whole stack on with host identity as the only required input. cmd/troupe(landed). The messaging binary carries the RFC-0015 §3 verbs (send/read/list/message) + the troupe MCP surface, over aTransport{local, xmpp}seam:local(default) is ringmaster'sjobwake/jobmcpsubstrate via a go-pkgs bridge (troupe consumes it, not a copy);xmpp(opt-in, RFC-0001) is the XMPP chat transport — atroupe agentdaemon over Prosody MUC rooms. Still ahead: the clown plugin that launches the agent per session, abatsconformance lane, and cross-host s2s federation.
Using the prosody module
Add troupe as a flake input and import the module:
{
inputs.troupe.url = "git+https://code.linenisgreat.com/troupe.git";
# In your home-manager config:
# imports = [ inputs.troupe.homeManagerModules.prosody ];
# services.prosody = {
# enable = true;
# domain = "myhost.xmpp.example.com";
# mucDomain = "muc-myhost.xmpp.example.com"; # one-label sibling for wildcard cover
# federated = false; # loopback bootstrap
# # To federate, flip `federated = true` and pin the bind address. Resolve a
# # join-time-dynamic headscale IP at service-start:
# # federatedInterfacesCommand =
# # "tailscale --socket=/run/tailscale-hs/tailscaled.sock ip";
# # or pin a deterministic per-host address:
# # federatedInterfaces = [ "100.96.0.5" "fd7a:115c:a1e0::5" ];
# certificate = "/home/me/.local/share/lego/certificates/_.xmpp.example.com.crt";
# key = "/home/me/.local/share/lego/certificates/_.xmpp.example.com.key";
# selfSignedBootstrap = false; # set true on a fresh host with no real cert
# admins = [ "agent@myhost.xmpp.example.com" ];
# lego = {
# enable = true;
# domains = [ "*.xmpp.example.com" ];
# tokenFile = "/home/me/.config/lego/cloudflare-token";
# email = "admin@example.com";
# };
# };
}
troupe XMPP transport (RFC-0001) — opt-in
Two options prepare the node for troupe's XMPP chat transport
(docs/rfcs/0001-xmpp-messaging-transport.md). Both default OFF, so an existing
consumer's generated config is unchanged until it opts in:
services.prosody = {
# ... the bootstrap config above ...
# RFC-0001 §3: replace the single `mucDomain` MUC component with three —
# personal./session. (ephemeral rooms, auto-destroyed when empty) and host.
# (persistent broadcast room). Domains default to personal./session./host. of
# `mucDomain`; set TROUPE_XMPP_MUC_DOMAIN = mucDomain on the agent side.
mucTiers.enable = true;
# RFC-0001 §2: a dedicated SASL-ANONYMOUS vhost (default `anon.<domain>`) so
# troupe agents connect with zero provisioning. The primary internal_hashed
# vhost (parley + seeded accounts) is untouched. Agents set this as
# TROUPE_XMPP_DOMAIN. Agents also support PLAIN, so anonymous is optional.
anonymous.enable = true;
};
The tier components are nested a label below mucDomain, so a one-label wildcard
cert does not cover them. That is fine for the loopback TROUPE_XMPP_INSECURE=1
test and for c2s (TLS is only to the c2s vhost); cross-host s2s needs broader cert
coverage and is a documented follow-up (see the cert caveat in nix/hm/prosody.nix).
See nix/hm/prosody.nix for the full option set. The parley moxin is exposed as
packages.<system>.moxins (a store path) — add it to MOXIN_PATH so parley.*
resolve; see moxins/parley/README.md.
Layout
flake.nix inputs + outputs (homeManagerModules, packages, devShell, checks)
nix/hm/prosody.nix the Prosody home-manager module
moxins/parley/ the parley XMPP MUC moxin (send/read)
cmd/troupe/ the troupe messaging binary (RFC-0015 surface; ringmaster-backed)
doc/troupe.1.scd troupe(1) manpage (scdoc)
go.mod Go module (github.com/amarbel-llc/troupe)
gomod2nix.toml go-pkgs dependency lock (zero third-party deps today)
justfile build/check/fmt + ops recipes (debug-prosody, lego-issue-cert, …)
version.env version SSOT
Reserved for later phases: nix/nixos/ (NixOS modules). See the roadmap above.
Development
just # build (parley + troupe) + flake check
just fmt # nixfmt via treefmt
just build # nix build .#parley .#troupe
Forge-native at code.linenisgreat.com/troupe (the source of truth).