Service to watch for creation of accounts on a PDS, and automatically add them as collaborators to a Tangled repository.
README.md

tangled-collaborator-sidecar #

BLANCMANGE-TAGLINE

How it fits together #

  didbot server (pds.did.bot)             owner PDS (Bluesky)
  +----------------------------+          +------------------------------+
  | subscribeRepos firehose    |          | org.tangled.repo.declaration |--> Jetstream
  | bot.did.listAccounts       |          | getServiceAuth               |      |
  | bot.did.listAccountLedgers |          | OAuth, confidential client   |      |
  +-------------+--------------+          +---------------+--------------+      |
                |                                         ^                     |
   key commits, |                    sessions, refreshes, |   repo creates and  |
   tombstones   |                    service-auth tokens  |   deletes           |
                v                                         v                     |
  +-----------------------------------------------------------------------+    |
  | tangled-collaborator-sidecar                                          |<---+
  | Lightsail container, tangled-collaborator-sidecar.did.bot             |
  |                                                                       |
  | known agents x known repos x rules  -->  grants, audit on stdout      |
  | in memory: agents, repos, tombstones; files: sealed sessions          |
  +---------+-------------------------------------------------------------+
            |
            | createRecord / deleteRecord
            | org.tangled.graph.collaborator
            | aud = repo DID, iss = repo owner
            v
  +------------------------------+
  | Tangled knot (knot1)         |<-- the agent pushes branches and opens pulls
  | the repo's collaborators     |
  +------------------------------+
  1. An agent on a claimed didbot server publishes an org.tangled.key.sshKey. The commit that creates it arrives on the server's firehose.
  2. The sidecar checks the agent's operator against the operator's own bot.did.operator records, and matches its zone and kind against the rules.
  3. For each repo the rules name, the sidecar asks that repo's owner's PDS for a service-auth token, and writes an org.tangled.graph.collaborator record on the repo's knot. The agent can then push and open pulls.
  4. A new repo arrives through Jetstream. It is granted to every known agent its rules match.
  5. Only an explicit tombstone revokes, whoever added the collaborator: a deleted account event, or a deprovisioned ledger entry. A missing or unreachable server never does.

docs/design.md has the details.

Running it #

Build and check the configuration:

cargo build --release
./target/release/tangled-collaborator-sidecar --config deploy/config.toml check-config

deploy/config.toml is the configuration, and the image bakes it in. It holds no secret: a change to it is a new image. It names the servers to follow, the operators, the owners, the rules, and the sidecar's own hostname and data directory. The committed data directory is /data, the container's volume, so a local run needs a copy with a writable one.

The ACME contact email is optional, and the committed configuration leaves it unset. Without tailnet_bind and tailnet_hostname, login, callback and status are served on the public listener.

The data directory holds the session data key, the OAuth client signing key, one sealed session file per owner under sessions/, and the ACME cache. Each session file holds the owner's consent time. All are mode 0600. Back it up: losing the signing key ends every owner session.

Commands:

tangled-collaborator-sidecar serve
tangled-collaborator-sidecar check-config
tangled-collaborator-sidecar true-up --dry-run
tangled-collaborator-sidecar status

Sign an owner in by opening https://<public_hostname>/login. /status is public and shows owner DIDs and states, the known agent, repo and tombstone counts, and whether each stream is connected. /health is public and always answers 200; its worst field is the worst component state, including expiring within 30 days of the two-year consent cap.

Deploying #

The image is published to ECR Public, and the host pulls it without a deploy script. The default repository is public.ecr.aws/i7w5y2e0/tangled-collaborator-sidecar; pass another, or a tfvars file that holds it as image_repository, to override:

scripts/publish-image.sh
scripts/publish-image.sh --var-file /path/to/didbot/infra/sidecar/terraform.tfvars

It builds a static musl binary for linux/amd64 in a container, then pushes the image tagged with the commit's short sha and latest. It refuses a dirty tree, and it does not push until the build succeeds.

The host is didbot's infra/sidecar Lightsail stack. Its first apply installs Docker, the data directory at /var/lib/tangled-collaborator-sidecar, and two systemd units. The main unit runs the container as a non-root UID with 443 published, /data mounted from the data directory, CAP_NET_BIND_SERVICE and a read-only filesystem. The timer pulls <image_repository>:latest every five minutes and restarts the container only when the image ID changed. There is no SSH deploy and no credential on the host.

Publishing never touches the data directory. The public certificate comes from Let's Encrypt on the instance and is cached there.