A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
81
fork

Configure Feed

Select the types of activity you want to include in your feed.

upcloud provision fixes and relay tweaks

+91 -19
+1
deploy/upcloud/configs/cloudinit.sh.tmpl
··· 18 18 export DEBIAN_FRONTEND=noninteractive 19 19 apt-get update && apt-get upgrade -y 20 20 apt-get install -y git gcc make curl libsqlite3-dev nodejs npm htop systemd-timesyncd 21 + sed -i 's/^#NTP=.*/NTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org/' /etc/systemd/timesyncd.conf 21 22 timedatectl set-ntp true 22 23 23 24 # Swap (for small instances)
+11 -1
deploy/upcloud/provision.go
··· 647 647 Comment: "Allow private network", 648 648 }, 649 649 { 650 + Direction: upcloud.FirewallRuleDirectionIn, 651 + Action: upcloud.FirewallRuleActionAccept, 652 + Family: upcloud.IPAddressFamilyIPv4, 653 + Protocol: upcloud.FirewallRuleProtocolUDP, 654 + SourcePortStart: "123", 655 + SourcePortEnd: "123", 656 + Position: 3, 657 + Comment: "Allow NTP replies", 658 + }, 659 + { 650 660 Direction: upcloud.FirewallRuleDirectionIn, 651 661 Action: upcloud.FirewallRuleActionDrop, 652 - Position: 3, 662 + Position: 4, 653 663 Comment: "Drop all other inbound", 654 664 }, 655 665 },
+22 -17
pkg/atproto/relays.go
··· 67 67 Online bool 68 68 Error string 69 69 HasRequestCrawl bool 70 + RequestCrawlStatus int // HTTP status code from probe (400=open, 401/403=auth required, 5xx=error) 70 71 HasListReposByCollection bool 71 72 RepoStatus *RepoStatus 72 73 HostStatus *HostStatus ··· 90 91 // Probe requestCrawl 91 92 go func() { 92 93 defer wg.Done() 93 - supported, online := probeRequestCrawl(relayURL) 94 + supported, statusCode, online := probeRequestCrawl(relayURL) 94 95 if online { 95 96 markOnline() 96 97 } 97 - if supported { 98 - mu.Lock() 99 - result.HasRequestCrawl = true 100 - mu.Unlock() 101 - } 98 + mu.Lock() 99 + result.HasRequestCrawl = supported 100 + result.RequestCrawlStatus = statusCode 101 + mu.Unlock() 102 102 }() 103 103 104 104 // Check host status ··· 158 158 return result 159 159 } 160 160 161 - // probeRequestCrawl checks if a relay supports the requestCrawl endpoint using a HEAD request. 162 - // A 4xx response (e.g. 405 Method Not Allowed) means the endpoint exists. 163 - // A 5xx or connection failure means it's broken or unsupported. 164 - func probeRequestCrawl(relayURL string) (supported bool, online bool) { 161 + // probeRequestCrawl checks if a relay supports the requestCrawl endpoint by POSTing 162 + // an empty hostname. Returns (supported, statusCode, online): 163 + // - 400 = endpoint exists and accepts unauthenticated crawls (supported=true) 164 + // - 401/403 = endpoint exists but requires auth (supported=false) 165 + // - 5xx = endpoint is broken (supported=false) 166 + // - connection error = relay offline (online=false) 167 + func probeRequestCrawl(relayURL string) (supported bool, statusCode int, online bool) { 165 168 client := &http.Client{Timeout: 5 * time.Second} 166 - req, err := http.NewRequest("HEAD", relayURL+SyncRequestCrawl, nil) 169 + body := bytes.NewReader([]byte(`{"hostname":""}`)) 170 + req, err := http.NewRequest("POST", relayURL+SyncRequestCrawl, body) 167 171 if err != nil { 168 - return false, false 172 + return false, 0, false 169 173 } 174 + req.Header.Set("Content-Type", "application/json") 170 175 171 176 resp, err := client.Do(req) 172 177 if err != nil { 173 - return false, false 178 + return false, 0, false 174 179 } 175 180 defer resp.Body.Close() 176 181 177 - // Any HTTP response means the relay is online. 178 - // 4xx (typically 405 Method Not Allowed) = endpoint exists. 179 - // 5xx = endpoint is broken. 180 - return resp.StatusCode >= 400 && resp.StatusCode < 500, true 182 + // 400 = endpoint exists, accepts unauthenticated requests (empty hostname rejected as expected) 183 + // 401/403 = endpoint exists but requires authentication 184 + // 5xx = endpoint is broken 185 + return resp.StatusCode == http.StatusBadRequest, resp.StatusCode, true 181 186 } 182 187 183 188 // probeListReposByCollection checks if a relay supports the listReposByCollection endpoint.
+48
pkg/atproto/relays_test.go
··· 217 217 func TestCheckRelayStatus_AllEndpointsSucceed(t *testing.T) { 218 218 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 219 219 switch r.URL.Path { 220 + case SyncRequestCrawl: 221 + w.WriteHeader(http.StatusBadRequest) // empty hostname = 400 220 222 case SyncGetHostStatus: 221 223 json.NewEncoder(w).Encode(HostStatus{Hostname: "hold.example.com", Active: true}) 222 224 case SyncGetRepoStatus: ··· 237 239 if status.Error != "" { 238 240 t.Errorf("expected no error, got %q", status.Error) 239 241 } 242 + if !status.HasRequestCrawl { 243 + t.Error("expected HasRequestCrawl = true") 244 + } 245 + if status.RequestCrawlStatus != http.StatusBadRequest { 246 + t.Errorf("RequestCrawlStatus = %d, want %d", status.RequestCrawlStatus, http.StatusBadRequest) 247 + } 240 248 if !status.HasListReposByCollection { 241 249 t.Error("expected HasListReposByCollection = true") 242 250 } ··· 257 265 func TestCheckRelayStatus_OnlineButUnknownHost(t *testing.T) { 258 266 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 259 267 switch r.URL.Path { 268 + case SyncRequestCrawl: 269 + w.WriteHeader(http.StatusBadRequest) // empty hostname = 400 260 270 case SyncGetHostStatus: 261 271 w.WriteHeader(http.StatusBadRequest) // relay doesn't know this host 262 272 case SyncGetRepoStatus: ··· 303 313 func TestCheckRelayStatus_NoListReposByCollection(t *testing.T) { 304 314 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 305 315 switch r.URL.Path { 316 + case SyncRequestCrawl: 317 + w.WriteHeader(http.StatusBadRequest) // empty hostname = 400 306 318 case SyncGetHostStatus: 307 319 json.NewEncoder(w).Encode(HostStatus{Hostname: "hold.example.com", Active: true}) 308 320 case SyncGetRepoStatus: ··· 327 339 t.Error("expected RepoStatus to be active") 328 340 } 329 341 } 342 + 343 + func TestCheckRelayStatus_AuthRequired(t *testing.T) { 344 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 345 + switch r.URL.Path { 346 + case SyncRequestCrawl: 347 + w.WriteHeader(http.StatusForbidden) // auth required 348 + case SyncGetHostStatus: 349 + json.NewEncoder(w).Encode(HostStatus{Hostname: "hold.example.com", Active: true}) 350 + case SyncGetRepoStatus: 351 + json.NewEncoder(w).Encode(RepoStatus{DID: "did:web:hold.example.com", Active: true, Rev: "r1"}) 352 + case SyncListReposByCollection: 353 + json.NewEncoder(w).Encode(map[string]any{"repos": []any{}}) 354 + default: 355 + w.WriteHeader(http.StatusNotFound) 356 + } 357 + })) 358 + defer srv.Close() 359 + 360 + status := CheckRelayStatus(srv.URL, "hold.example.com", "did:web:hold.example.com") 361 + 362 + if !status.Online { 363 + t.Error("expected Online = true") 364 + } 365 + if status.HasRequestCrawl { 366 + t.Error("expected HasRequestCrawl = false (auth required)") 367 + } 368 + if status.RequestCrawlStatus != http.StatusForbidden { 369 + t.Errorf("RequestCrawlStatus = %d, want %d", status.RequestCrawlStatus, http.StatusForbidden) 370 + } 371 + if !status.HasListReposByCollection { 372 + t.Error("expected HasListReposByCollection = true") 373 + } 374 + if status.RepoStatus == nil || !status.RepoStatus.Active { 375 + t.Error("expected RepoStatus to be active") 376 + } 377 + }
+2
pkg/hold/admin/handlers_relays.go
··· 22 22 Online bool 23 23 Error string 24 24 HasRequestCrawl bool 25 + RequestCrawlStatus int 25 26 HasListReposByCollection bool 26 27 RepoStatus *atproto.RepoStatus 27 28 HostStatus *atproto.HostStatus ··· 77 78 Online: status.Online, 78 79 Error: status.Error, 79 80 HasRequestCrawl: status.HasRequestCrawl, 81 + RequestCrawlStatus: status.RequestCrawlStatus, 80 82 HasListReposByCollection: status.HasListReposByCollection, 81 83 RepoStatus: status.RepoStatus, 82 84 HostStatus: status.HostStatus,
+7 -1
pkg/hold/admin/templates/partials/relay_status.html
··· 22 22 <td> 23 23 <div class="flex flex-wrap gap-1"> 24 24 {{if .Online}} 25 - {{if .HasRequestCrawl}}<span class="badge badge-ghost badge-sm">requestCrawl</span>{{end}} 25 + {{if .HasRequestCrawl}} 26 + <span class="badge badge-ghost badge-sm">requestCrawl</span> 27 + {{else if or (eq .RequestCrawlStatus 401) (eq .RequestCrawlStatus 403)}} 28 + <span class="badge badge-warning badge-sm">requestCrawl (auth required)</span> 29 + {{else if ge .RequestCrawlStatus 500}} 30 + <span class="badge badge-error badge-sm">requestCrawl ({{.RequestCrawlStatus}})</span> 31 + {{end}} 26 32 {{if .HasListReposByCollection}}<span class="badge badge-ghost badge-sm">listReposByCollection</span>{{end}} 27 33 {{else}} 28 34 <span class="text-base-content/30 text-sm">-</span>