Monorepo for Tangled tangled.org

knotserver/config: add PLC URL config option #682

closed opened by shailpatels.me targeting master from shailpatels.me/core: knotserver_configurable_plc

The knotserver can now use alternative PLCs for DID resolution by setting the env var KNOT_SERVER_PLC_URL. The default identity directory was copied out of the at proto lib and updated to take in a target url for the PLC being used to do this.

Labels

None yet.

assignee

None yet.

Participants 3
AT URI
at://did:plc:fjoxhdnj2kep6llsvhtxqrk3/sh.tangled.repo.pull/3m3gzy4m25a22
+34 -7
Diff #1
+1 -1
guard/guard.go
··· 196 196 } 197 197 198 198 func resolveIdentity(ctx context.Context, l *slog.Logger, didOrHandle string) *identity.Identity { 199 - resolver := idresolver.DefaultResolver() 199 + resolver := idresolver.DefaultResolver(identity.DefaultPLCURL) 200 200 ident, err := resolver.ResolveIdent(ctx, didOrHandle) 201 201 if err != nil { 202 202 l.Error("Error resolving handle", "error", err, "handle", didOrHandle)
+28 -2
idresolver/resolver.go
··· 42 42 return &base 43 43 } 44 44 45 + func DefaultDirectory(plcUrl string) identity.Directory { 46 + base := identity.BaseDirectory{ 47 + PLCURL: plcUrl, 48 + HTTPClient: http.Client{ 49 + Timeout: time.Second * 10, 50 + Transport: &http.Transport{ 51 + // would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad. 52 + IdleConnTimeout: time.Millisecond * 1000, 53 + MaxIdleConns: 100, 54 + }, 55 + }, 56 + Resolver: net.Resolver{ 57 + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 58 + d := net.Dialer{Timeout: time.Second * 3} 59 + return d.DialContext(ctx, network, address) 60 + }, 61 + }, 62 + TryAuthoritativeDNS: true, 63 + // primary Bluesky PDS instance only supports HTTP resolution method 64 + SkipDNSDomainSuffixes: []string{".bsky.social"}, 65 + UserAgent: "indigo-identity/" + versioninfo.Short(), 66 + } 67 + cached := identity.NewCacheDirectory(&base, 250_000, time.Hour*24, time.Minute*2, time.Minute*5) 68 + return &cached 69 + } 70 + 45 71 func RedisDirectory(url string) (identity.Directory, error) { 46 72 hitTTL := time.Hour * 24 47 73 errTTL := time.Second * 30 ··· 49 75 return redisdir.NewRedisDirectory(BaseDirectory(), url, hitTTL, errTTL, invalidHandleTTL, 10000) 50 76 } 51 77 52 - func DefaultResolver() *Resolver { 78 + func DefaultResolver(plcUrl string) *Resolver { 53 79 return &Resolver{ 54 - directory: identity.DefaultDirectory(), 80 + directory: DefaultDirectory(plcUrl), 55 81 } 56 82 } 57 83
+1
knotserver/config/config.go
··· 22 22 JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"` 23 23 Owner string `env:"OWNER, required"` 24 24 LogDids bool `env:"LOG_DIDS, default=true"` 25 + PlcUrl string `env:"PLC_URL, default=https://plc.directory"` 25 26 26 27 // This disables signature verification so use with caution. 27 28 Dev bool `env:"DEV, default=false"`
+2 -2
knotserver/ingester.go
··· 120 120 } 121 121 122 122 // resolve this aturi to extract the repo record 123 - resolver := idresolver.DefaultResolver() 123 + resolver := idresolver.DefaultResolver(h.c.Server.PlcUrl) 124 124 ident, err := resolver.ResolveIdent(ctx, repoAt.Authority().String()) 125 125 if err != nil || ident.Handle.IsInvalidHandle() { 126 126 return fmt.Errorf("failed to resolve handle: %w", err) ··· 233 233 return err 234 234 } 235 235 236 - resolver := idresolver.DefaultResolver() 236 + resolver := idresolver.DefaultResolver(h.c.Server.PlcUrl) 237 237 238 238 subjectId, err := resolver.ResolveIdent(ctx, record.Subject) 239 239 if err != nil || subjectId.Handle.IsInvalidHandle() {
+1 -1
knotserver/internal.go
··· 145 145 146 146 func (h *InternalHandle) replyCompare(line git.PostReceiveLine, repoOwner string, gitRelativeDir string, repoName string, ctx context.Context) ([]string, error) { 147 147 l := h.l.With("handler", "replyCompare") 148 - userIdent, err := idresolver.DefaultResolver().ResolveIdent(ctx, repoOwner) 148 + userIdent, err := idresolver.DefaultResolver(h.c.Server.PlcUrl).ResolveIdent(ctx, repoOwner) 149 149 user := repoOwner 150 150 if err != nil { 151 151 l.Error("Failed to fetch user identity", "err", err)
+1 -1
knotserver/router.go
··· 36 36 l: log.FromContext(ctx), 37 37 jc: jc, 38 38 n: n, 39 - resolver: idresolver.DefaultResolver(), 39 + resolver: idresolver.DefaultResolver(c.Server.PlcUrl), 40 40 } 41 41 42 42 err := e.AddKnot(rbac.ThisServer)

History

2 rounds 5 comments
sign up or login to add to the discussion
1 commit
expand
knotserver/config: add PLC URL config option
expand 4 comments

thanks for the PR! if i am understanding correctly:

  • this PR introduces a new idresolver.DefaultDirectory
  • this is subsequently used in idresolver.DefaultResolver

the issue here is that users of the redis based resolver will be unable to overload their PLC URL (it uses idresolver.BaseDirectory internally). i would suggest that:

  • we remove idresolver.DefaultDirectory
  • add the PLC URL overload to idresolver.BaseDirectory
  • permit PLC URL overload on users of idresolver.RedisDirectory

@oppi.li I've been working on same feature but for appview side, I'll open a new PR merging both with your review applied.

closing in flavor of #683

Yeah exactly, I pulled the defaultDirectory out of the library https://github.com/bluesky-social/indigo/blob/main/atproto/identity/directory.go#L65 and added the url parameter.

Thanks for the other PR @boltless.me!

closed without merging
1 commit
expand
knotserver/config: add PLC URL config option
expand 1 comment

The original issue was related to DID resolution with ipv6 only https://tangled.org/@tangled.org/core/issues/268,

I setup my knot with the env KNOT_SERVER_PLC_URL=https://plc.wtf and got a little farther but looks like the PDS also doesn't support IPV6 ;-;

Oct 18 04:43:30 debian-2gb-ash-1 knot[34274]: 2025/10/18 04:43:30 DEBU knot/server/serviceauth: valid signature ActorDid=did:plc:fjoxhdnj2kep6llsvhtxqrk3
Oct 18 04:43:30 debian-2gb-ash-1 knot[34274]: 2025/10/18 04:43:30 DEBU knot: performing request subsystem=RobustHTTPClient method=GET url="https://russula.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?collection=sh.tangled.repo&repo=did%3Aplc%3Afjoxhdnj2kep6llsvhtxqrk3&rkey=3m3gxyvkylt22"
Oct 18 04:43:34 debian-2gb-ash-1 knot[34274]: 2025/10/18 04:43:34 WARN knot: request failed subsystem=RobustHTTPClient error="Get \"https://russula.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?collection=sh.tangled.repo&repo=did%3Aplc%3Afjoxhdnj2kep6llsvhtxqrk3&rkey=3m3gxyvkylt22\": context canceled" method=GET url="https://russula.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?collection=sh.tangled.repo&repo=did%3Aplc%3Afjoxhdnj2kep6llsvhtxqrk3&rkey=3m3gxyvkylt22"
Oct 18 04:43:34 debian-2gb-ash-1 knot[34274]: 2025/10/18 04:43:34 ERRO knot/server/xrpc: failed handler=NewRepo kind=Generic error="request failed: Get \"https://russula.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?collection=sh.tangled.repo&repo=did%3Aplc%3Afjoxhdnj2kep6llsvhtxqrk3&rkey=3m3gxyvkylt22\": GET https://russula.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?collection=sh.tangled.repo&repo=did%3Aplc%3Afjoxhdnj2kep6llsvhtxqrk3&rkey=3m3gxyvkylt22 giving up after 1 attempt(s): context canceled"
shail@debian13:~/tangled/core$ dig russula.us-west.host.bsky.network AAAA @8.8.8.8

; <<>> DiG 9.20.11-4-Debian <<>> russula.us-west.host.bsky.network AAAA @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12857
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;russula.us-west.host.bsky.network. IN	AAAA

;; AUTHORITY SECTION:
bsky.network.		42	IN	SOA	ns-61.awsdns-07.com. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 7200

;; Query time: 27 msec
;; SERVER: 8.8.8.8#53(8.8.8.8) (UDP)
;; WHEN: Sat Oct 18 01:24:50 EDT 2025
;; MSG SIZE  rcvd: 142