diff --git a/.gitignore b/.gitignore
index c7f2b31bf..d41aec61c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,3 +80,4 @@ CLAUDE.md
# MkDocs GitHub metrics cache (generated at build time)
docs/mkdocs/.cache/
+docs/content/javascripts/dir-repobeats-data.js
diff --git a/docs/content/dir/.index b/docs/content/dir/.index
index 4a0905095..7d43b9186 100644
--- a/docs/content/dir/.index
+++ b/docs/content/dir/.index
@@ -1,6 +1,7 @@
nav:
- Get Started:
- Overview: dir-overview.md
+ - Choosing a Setup: dir-choosing-a-setup.md
- Quickstart: dir-quickstart.md
- Usage Guide: dir-features-scenarios.md
- Concepts:
diff --git a/docs/content/dir/dir-choosing-a-setup.md b/docs/content/dir/dir-choosing-a-setup.md
new file mode 100644
index 000000000..d24d84f86
--- /dev/null
+++ b/docs/content/dir/dir-choosing-a-setup.md
@@ -0,0 +1,139 @@
+# Choosing Your Directory Setup
+
+Not sure which Directory setup you need? Walk through the questions below, then find the
+matching configuration — each shown as its own topology.
+
+
+
+
Who needs to discover your records? Only you → keep going · your team or organization → Networked · other organizations → Federated.
+
(Only you) Should others be able to pull them from a public registry? No → Private · Yes → Public-store.
+
+
+
+## Recommended configurations
+
+The configurations below cover the useful combinations. In every diagram
+dashed lines mean discovery (only CIDs/labels move), solid lines mean retrieval (record bytes move).
+
+### Private node { #private-node }
+
+Keep records to yourself. Store locally, no network.
+
+You store, search, sign, and pull locally. You can still pull records
+inbound, both from a public registry and from a remote Directory node you authenticate to.
+
+What stays true either way: nobody else can discover or retrieve **your** records.
+
+```mermaid
+flowchart LR
+ user(["You · dirctl"]) -->|"push · search · pull"| api
+ user -->|"🔑 logged in"| peer
+ subgraph node["Your node"]
+ api["API server"] --> store[("Local store not network-reachable")]
+ end
+ reg[("Public OCI registry")] -->|"sync create --registry"| api
+ peer["Remote Directory node"] -->|"sync create"| api
+```
+
+Choose when:
+
+- Discovery = only you
+- Store = bundled/local
+
+See [Local Deployment](dir-deployment-local.md) for more details.
+
+### Public-store node { #public-store-node }
+
+Let others retrieve, without a network.
+
+The store is a public registry (GHCR, Docker Hub). Anyone who knows the registry can pull
+records straight from it — a specific CID or all of them — with `dirctl sync create
+--registry`; your node need not even be online. This still isn't network discovery: there's no
+DHT, so consumers point sync at your registry rather than finding you across a network.
+
+```mermaid
+flowchart LR
+ subgraph node["Your node"]
+ api["API server"]
+ end
+ api -->|"records stored here"| reg[("Public OCI registry GHCR · Docker Hub")]
+ other(["Another node"]) -->|"sync create --registry"| reg
+```
+
+Choose when:
+
+- Discovery = only you
+- Store = public registry
+
+See [Local Deployment](dir-deployment-local.md) and [Store](dir-component-store.md) for more details.
+
+### Networked node { #networked-node }
+
+Be discoverable.
+
+A bootstrap connection puts the node on the DHT: it announces its records and can search
+for records held by other nodes.
+
+Being networked also enables autosync — direct node-to-node transfer over libp2p. Autosync
+is receiver-controlled: each node automatically pulls only from the peers in its own
+trusted allow-list. So you autosync records from peers you list, and a peer that lists
+you autosyncs yours. It is opt-in and off by default.
+
+Because it is receiver-controlled, your own autosync setting changes what you can pull in —
+not what others can take from you:
+
+| Your store | Your autosync | Can others retrieve your records? | Can you retrieve others' records? |
+|---|:--:|---|---|
+| Local | off | Only a peer that lists you in its own allow-list, over libp2p | On-demand `sync create` only |
+| Local | on | Same — your autosync does not change this | Yes, from peers in your own allow-list (+ on-demand) |
+| Public | off | Yes — anyone pulls from the registry | On-demand `sync create` only |
+| Public | on | Yes — anyone pulls from the registry | Yes, from peers in your own allow-list (+ on-demand) |
+
+```mermaid
+flowchart TB
+ subgraph node["Your node · bootstrapped"]
+ api["API server"] --> store[("store · local or public")]
+ end
+ dht{{"P2P network · DHT"}}
+ peer(["Peer that trusts you"])
+ api -. "1 · announce CIDs" .-> dht
+ dht -. "2 · discovers your CIDs" .-> peer
+ peer -->|"3 · autosync pulls record (direct, libp2p)"| api
+```
+
+*Choose when:*
+
+- Discovery = within your team or organization
+- Store = local or public
+
+See [Local Deployment](dir-deployment-local.md), [Connecting to a Remote Directory](dir-deployment-local.md#connecting-to-a-remote-directory), and [Routing](dir-component-routing.md) for more details.
+
+### Federated { #federated }
+
+Exchange across organizations.
+
+Multiple production nodes peer under a shared trust root. Organizations discover each
+other's records over a shared DHT and retrieve them with authenticated, authorized access
+(SPIFFE mTLS + authorization policies). Establishing that shared trust root takes a one-time
+SPIRE federation step, where each organization's SPIRE server exchanges trust bundles with
+the others (see [Federation Bundle Profiles](dir-federation-profiles.md)).
+
+```mermaid
+flowchart LR
+ dht{{"Shared DHT · GossipSub"}}
+ subgraph orgA["Organization A"]
+ a(["Node A"])
+ end
+ subgraph orgB["Organization B"]
+ b(["Node B"])
+ end
+ dht <-. "announce + discover" .-> a
+ dht <-. "announce + discover" .-> b
+ a <-->|"retrieve · SPIFFE mTLS + authz"| b
+```
+
+Choose when:
+
+- Discovery = other organizations
+
+See [Federation](dir-federation-overview.md) and [Trust Model](dir-component-trust-model.md) for more details.
diff --git a/docs/content/javascripts/dir-repobeats-data.js b/docs/content/javascripts/dir-repobeats-data.js
deleted file mode 100644
index 2186f2cfa..000000000
--- a/docs/content/javascripts/dir-repobeats-data.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/* Generated by MkDocs hooks on build — do not edit by hand. */
-window.__DIR_REPOBEATS_DATA__ = {"repo":"agntcy/dir","recentCommits":150,"commitBars":[31,31,16,16,17,17,38,38,41,41,38,38],"openPrs":16,"prsOpened":138,"prsMerged":127,"issuesOpened":51,"issuesClosed":67,"issueWeeks":[17,38,41,38],"prWeeks":[17,38,41,38],"contributors":[{"login":"paralta","avatar_url":"https://avatars.githubusercontent.com/u/46568597?v=4","contributions":215},{"login":"agntcy-automation[bot]","avatar_url":"https://avatars.githubusercontent.com/in/3133123?v=4","contributions":153},{"login":"tkircsi","avatar_url":"https://avatars.githubusercontent.com/u/28192787?v=4","contributions":124},{"login":"ramizpolic","avatar_url":"https://avatars.githubusercontent.com/u/32913827?v=4","contributions":118},{"login":"arpad-csepi","avatar_url":"https://avatars.githubusercontent.com/u/21104922?v=4","contributions":99},{"login":"adamtagscherer","avatar_url":"https://avatars.githubusercontent.com/u/11526157?v=4","contributions":82},{"login":"csirmazbendeguz","avatar_url":"https://avatars.githubusercontent.com/u/19559023?v=4","contributions":67},{"login":"akijakya","avatar_url":"https://avatars.githubusercontent.com/u/39521244?v=4","contributions":46},{"login":"muscariello","avatar_url":"https://avatars.githubusercontent.com/u/168545?v=4","contributions":40},{"login":"build-agntcy","avatar_url":"https://avatars.githubusercontent.com/u/198542035?v=4","contributions":18},{"login":"dependabot[bot]","avatar_url":"https://avatars.githubusercontent.com/in/29110?v=4","contributions":16},{"login":"keraron","avatar_url":"https://avatars.githubusercontent.com/u/53857116?v=4","contributions":14}]};
diff --git a/docs/content/javascripts/dir-setup-chooser.js b/docs/content/javascripts/dir-setup-chooser.js
new file mode 100644
index 000000000..dcc1f9c81
--- /dev/null
+++ b/docs/content/javascripts/dir-setup-chooser.js
@@ -0,0 +1,237 @@
+/* Copyright AGNTCY Contributors (https://github.com/agntcy) */
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/* Interactive "choose your setup" wizard for the Configurations page.
+ Only two things decide the configuration: how far your records need to be
+ discoverable, and (when they don't) whether others may pull them. So this is
+ a 1-2 step conditional flow that maps 1:1 to the four configurations and
+ deep-links to each section on the same page. Progressive enhancement:
+ replaces the static fallback list inside #setup-chooser. */
+(function () {
+ var SECTION_IDS = ["private-node", "public-store-node", "networked-node", "federated"];
+ var activeWrap = null;
+ var activeTimer = null;
+
+ function getSectionNodes(heading) {
+ var nodes = [heading];
+ var el = heading.nextElementSibling;
+ while (el && el.tagName !== "H3") {
+ nodes.push(el);
+ el = el.nextElementSibling;
+ }
+ return nodes;
+ }
+
+ function unwrapSection(wrap) {
+ if (!wrap || !wrap.parentNode) {
+ return;
+ }
+ var parent = wrap.parentNode;
+ while (wrap.firstChild) {
+ parent.insertBefore(wrap.firstChild, wrap);
+ }
+ wrap.remove();
+ }
+
+ function clearSectionHighlight() {
+ if (activeTimer) {
+ clearTimeout(activeTimer);
+ activeTimer = null;
+ }
+ if (activeWrap) {
+ unwrapSection(activeWrap);
+ activeWrap = null;
+ }
+ }
+
+ function highlightSection(id) {
+ clearSectionHighlight();
+ var heading = document.getElementById(id);
+ if (!heading) {
+ return;
+ }
+
+ var nodes = getSectionNodes(heading);
+ var wrap = document.createElement("div");
+ wrap.className = "sc-section-target sc-section-flash";
+ heading.parentNode.insertBefore(wrap, heading);
+ nodes.forEach(function (node) {
+ wrap.appendChild(node);
+ });
+ activeWrap = wrap;
+
+ wrap.scrollIntoView({ behavior: "smooth", block: "start" });
+
+ activeTimer = setTimeout(function () {
+ wrap.classList.remove("sc-section-flash");
+ activeTimer = null;
+ }, 2600);
+ }
+
+ function flashFromHash() {
+ var id = location.hash.slice(1);
+ if (SECTION_IDS.indexOf(id) === -1) {
+ return;
+ }
+ requestAnimationFrame(function () {
+ highlightSection(id);
+ });
+ }
+
+ if (!window.__dirSetupSectionFlash) {
+ window.__dirSetupSectionFlash = true;
+ window.addEventListener("hashchange", flashFromHash);
+ }
+
+ document$.subscribe(function () {
+ flashFromHash();
+
+ var mount = document.getElementById("setup-chooser");
+ if (!mount) {
+ return;
+ }
+
+ var DISCOVER = {
+ key: "discover",
+ title: "Who needs to discover your records?",
+ hint: "Discovery = others finding out what records you have, and you discovering what others have, over the network.",
+ options: [
+ { v: "onlyme", label: "Only me", sub: "No one else needs to find them." },
+ { v: "team", label: "My team or organization", sub: "Nodes and teammates connected to a shared DHT." },
+ { v: "orgs", label: "Other organizations", sub: "Cross-organization discovery and exchange." },
+ ],
+ };
+ var STORE = {
+ key: "store",
+ title: "Should others be able to pull your records?",
+ hint: "Directly from a public registry (GHCR / Docker Hub), by CID — no network needed.",
+ options: [
+ { v: "local", label: "No — keep them local", sub: "Bundled local store; not network-reachable." },
+ { v: "public", label: "Yes — via a public registry", sub: "Anyone with the registry can pull them." },
+ ],
+ };
+
+ var CONFIGS = {
+ privatenode: { n: 1, name: "Private node", href: "#private-node", why: "Records stay on your machine — no one else can discover or retrieve them." },
+ publicstore: { n: 2, name: "Public-store node", href: "#public-store-node", why: "A public registry lets anyone pull your records by CID — without a network." },
+ networked: { n: 3, name: "Networked node", href: "#networked-node", why: "On the DHT you announce your records and can discover others across the network." },
+ federated: { n: 4, name: "Federated", href: "#federated", why: "Peered nodes exchange records across organizations under a shared trust root." },
+ };
+
+ var state = {};
+ var screen = "discover"; // discover | store | result
+
+ function pick(s) {
+ if (s.discover === "orgs") return "federated";
+ if (s.discover === "team") return "networked";
+ return s.store === "public" ? "publicstore" : "privatenode";
+ }
+
+ function esc(s) {
+ return String(s).replace(/[&<>"]/g, function (c) {
+ return { "&": "&", "<": "<", ">": ">", '"': """ }[c];
+ });
+ }
+
+ function optionsHTML(q) {
+ var chosen = state[q.key];
+ return q.options
+ .map(function (o) {
+ var on = chosen === o.v;
+ return (
+ '"
+ );
+ })
+ .join("");
+ }
+
+ function questionHTML(q, count, nextLabel, canBack) {
+ return (
+ '
' +
+ '
' + esc(count) + "
" +
+ '
' + esc(q.title) + "
" +
+ '
' + esc(q.hint) + "
" +
+ '
' + optionsHTML(q) + "
" +
+ '
' +
+ '" +
+ '" +
+ "
"
+ );
+ }
+
+ function render() {
+ if (screen === "discover") {
+ var toResult = state.discover && state.discover !== "onlyme";
+ mount.innerHTML = questionHTML(DISCOVER, "Question 1", toResult ? "See recommendation" : "Next", false);
+ return;
+ }
+ if (screen === "store") {
+ mount.innerHTML = questionHTML(STORE, "Question 2", "See recommendation", true);
+ return;
+ }
+ // result — just the recommendation and two actions
+ var c = CONFIGS[pick(state)];
+ mount.innerHTML =
+ '