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

Configure Feed

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

updated favicons, fix domain rerouting, fix deploy provisioning

+180 -32
+52
deploy/upcloud/provision.go
··· 256 256 saveState(state) 257 257 } 258 258 259 + // Always reconcile forwarded headers rule (handles existing LBs) 260 + if err := ensureLBForwardedHeaders(ctx, svc, state.LB.UUID); err != nil { 261 + return fmt.Errorf("LB forwarded headers: %w", err) 262 + } 263 + 259 264 // Always reconcile TLS certs (handles partial failures and re-runs) 260 265 tlsDomains := []string{cfg.BaseDomain} 261 266 tlsDomains = append(tlsDomains, cfg.RegistryDomains...) ··· 567 572 }, 568 573 Rules: []request.LoadBalancerFrontendRule{ 569 574 { 575 + Name: "set-forwarded-headers", 576 + Priority: 1, 577 + Matchers: []upcloud.LoadBalancerMatcher{}, 578 + Actions: []upcloud.LoadBalancerAction{ 579 + request.NewLoadBalancerSetForwardedHeadersAction(), 580 + }, 581 + }, 582 + { 570 583 Name: "route-hold", 571 584 Priority: 10, 572 585 Matchers: []upcloud.LoadBalancerMatcher{ ··· 716 729 } 717 730 fmt.Printf(" TLS certificate: %s\n", domain) 718 731 } 732 + 733 + return nil 734 + } 735 + 736 + // ensureLBForwardedHeaders ensures the "https" frontend has a set_forwarded_headers rule. 737 + // This makes the LB set X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Port headers, 738 + // overwriting any pre-existing values (prevents spoofing). 739 + func ensureLBForwardedHeaders(ctx context.Context, svc *service.Service, lbUUID string) error { 740 + rules, err := svc.GetLoadBalancerFrontendRules(ctx, &request.GetLoadBalancerFrontendRulesRequest{ 741 + ServiceUUID: lbUUID, 742 + FrontendName: "https", 743 + }) 744 + if err != nil { 745 + return fmt.Errorf("get frontend rules: %w", err) 746 + } 747 + 748 + for _, r := range rules { 749 + if r.Name == "set-forwarded-headers" { 750 + fmt.Println(" Forwarded headers rule: exists") 751 + return nil 752 + } 753 + } 754 + 755 + _, err = svc.CreateLoadBalancerFrontendRule(ctx, &request.CreateLoadBalancerFrontendRuleRequest{ 756 + ServiceUUID: lbUUID, 757 + FrontendName: "https", 758 + Rule: request.LoadBalancerFrontendRule{ 759 + Name: "set-forwarded-headers", 760 + Priority: 1, 761 + Matchers: []upcloud.LoadBalancerMatcher{}, 762 + Actions: []upcloud.LoadBalancerAction{ 763 + request.NewLoadBalancerSetForwardedHeadersAction(), 764 + }, 765 + }, 766 + }) 767 + if err != nil { 768 + return fmt.Errorf("create forwarded headers rule: %w", err) 769 + } 770 + fmt.Println(" Forwarded headers rule: created") 719 771 720 772 return nil 721 773 }
pkg/appview/public/apple-touch-icon.png

This is a binary file and will not be displayed.

pkg/appview/public/atcr.png

This is a binary file and will not be displayed.

pkg/appview/public/favicon-96x96.png

This is a binary file and will not be displayed.

pkg/appview/public/favicon.ico

This is a binary file and will not be displayed.

+76 -15
pkg/appview/public/favicon.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512"><svg version="1.1" id="svg1" width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> 2 - <defs id="defs1"> 3 - <rect x="264" y="264" width="203.43417" height="218.82669" id="rect63"></rect> 4 - </defs> 5 - <g id="g1"> 6 - <rect style="fill:#5ecac1;fill-opacity:1;stroke:#000000;stroke-width:18.0441;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" id="rect16" width="246.95587" height="246.95584" x="9.0220766" y="9.0220766"></rect> 7 - <rect style="fill:#254365;fill-opacity:1;stroke:#000000;stroke-width:18.0441;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" id="rect17" width="246.95587" height="246.95584" x="9.0220766" y="256.02206"></rect> 8 - <rect style="fill:#f29b54;fill-opacity:1;stroke:#000000;stroke-width:18.0012;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" id="rect18" width="246.99879" height="246.99883" x="256.00061" y="9.0005999"></rect> 9 - <rect style="display:inline;fill:#47a5d8;fill-opacity:1;stroke:#000000;stroke-width:18.0441;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" id="rect19" width="246.95586" height="246.95587" x="256.02206" y="256.02206"></rect> 10 - <path id="use60" style="fill:#193045;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" d="m 309,42 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z"></path> 11 - <path id="use60-1" style="fill:#193045;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" d="m 57,42 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 C 66,46.46 61.986,42 57,42 Z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 52 c 0,-5.54 -4.014,-10 -9,-10 z"></path> 12 - <path id="use60-3" style="fill:#193045;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" d="m 57,288 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 298 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 298 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 298 c 0,-5.54 -4.014,-10 -9,-10 z m 48,0 c -4.986,0 -9,4.46 -9,10 v 160 c 0,5.54 4.014,10 9,10 4.986,0 9,-4.46 9,-10 V 298 c 0,-5.54 -4.014,-10 -9,-10 z"></path> 13 - <text xml:space="preserve" id="text63" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:151.33px;font-family:'Noto Sans Telugu';-inkscape-font-specification:'Noto Sans Telugu';text-align:center;writing-mode:lr-tb;direction:ltr;white-space:pre;shape-inside:url(#rect63);shape-padding:0;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" x="126" y="0" transform="matrix(1.4849596,0,0,1.4775815,-169.00723,-145.87951)"><tspan x="300.94749" y="401.69141" id="tspan2"><tspan style="font-style:italic;font-weight:bold;font-family:'Droid Sans Thai';-inkscape-font-specification:'Droid Sans Thai Bold Italic'" id="tspan1">@</tspan></tspan></text> 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <svg 3 + version="1.1" 4 + width="511.99997" 5 + height="512" 6 + id="svg5" 7 + xmlns="http://www.w3.org/2000/svg" 8 + xmlns:svg="http://www.w3.org/2000/svg"> 9 + <defs 10 + id="defs5" /> 11 + <g 12 + id="g1"> 13 + <rect 14 + style="fill:#5ecac1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" 15 + id="teal" 16 + width="256" 17 + height="256" 18 + x="0" 19 + y="0" /> 20 + <rect 21 + style="fill:#254365;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" 22 + id="blue-dark" 23 + width="256" 24 + height="256" 25 + x="0" 26 + y="256" /> 27 + <rect 28 + style="fill:#f29b54;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" 29 + id="orange" 30 + width="256" 31 + height="256" 32 + x="256" 33 + y="0" 34 + ry="0" /> 35 + <rect 36 + style="display:inline;fill:#47a5d8;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke" 37 + id="blue-light" 38 + width="256" 39 + height="256" 40 + x="256" 41 + y="256" /> 42 + <path 43 + id="use60" 44 + style="fill:#193045;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" 45 + d="M 313.33334,40 C 308.16267,40 304,44.162668 304,49.333334 V 198.66665 c 0,5.17067 4.16267,9.33335 9.33334,9.33335 5.17065,0 9.33332,-4.16268 9.33332,-9.33335 V 49.333334 C 322.66666,44.162668 318.50399,40 313.33334,40 Z m 49.77777,0 c -5.17066,0 -9.33333,4.162668 -9.33333,9.333334 V 198.66665 c 0,5.17067 4.16267,9.33335 9.33333,9.33335 5.17066,0 9.33333,-4.16268 9.33333,-9.33335 V 49.333334 C 372.44444,44.162668 368.28177,40 363.11111,40 Z m 49.77778,0 c -5.17066,0 -9.33333,4.162668 -9.33333,9.333334 V 198.66665 c 0,5.17067 4.16267,9.33335 9.33333,9.33335 5.17066,0 9.33333,-4.16268 9.33333,-9.33335 V 49.333334 C 422.22222,44.162668 418.05955,40 412.88889,40 Z m 49.77777,0 c -5.17065,0 -9.33332,4.162668 -9.33332,9.333334 V 198.66665 c 0,5.17067 4.16267,9.33335 9.33332,9.33335 C 467.83733,208 472,203.83732 472,198.66665 V 49.333334 C 472,44.162668 467.83733,40 462.66666,40 Z" /> 46 + <path 47 + id="use60-1" 48 + style="fill:#193045;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" 49 + d="M 49.333334,40 C 44.162667,40 40,44.162668 40,49.333333 V 198.66664 C 40,203.83732 44.162667,208 49.333334,208 c 5.170665,0 9.333333,-4.16268 9.333333,-9.33336 V 49.333333 C 58.666667,44.162668 54.503999,40 49.333334,40 Z m 49.777781,0 c -5.170672,0 -9.333336,4.162668 -9.333336,9.333333 V 198.66664 c 0,5.17068 4.162664,9.33336 9.333336,9.33336 5.170645,0 9.333325,-4.16268 9.333325,-9.33336 V 49.333333 C 108.44444,44.162668 104.28176,40 99.111115,40 Z m 49.777765,0 c -5.17064,0 -9.33332,4.162668 -9.33332,9.333333 V 198.66664 c 0,5.17068 4.16268,9.33336 9.33332,9.33336 5.17066,0 9.33334,-4.16268 9.33334,-9.33336 V 49.333333 C 158.22222,44.162668 154.05954,40 148.88888,40 Z m 49.77777,0 c -5.17064,0 -9.33332,4.162668 -9.33332,9.333333 V 198.66664 c 0,5.17068 4.16268,9.33336 9.33332,9.33336 C 203.83733,208 208,203.83732 208,198.66664 V 49.333333 C 208,44.162668 203.83733,40 198.66665,40 Z" /> 50 + <path 51 + id="use60-3" 52 + style="fill:#193045;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0;paint-order:normal" 53 + d="M 49.333334,304 C 44.162667,304 40,308.16267 40,313.33334 V 462.66666 C 40,467.83733 44.162667,472 49.333334,472 c 5.170665,0 9.333333,-4.16267 9.333333,-9.33334 V 313.33334 C 58.666667,308.16267 54.503999,304 49.333334,304 Z m 49.777777,0 c -5.170668,0 -9.333332,4.16267 -9.333332,9.33334 v 149.33332 c 0,5.17067 4.162664,9.33334 9.333332,9.33334 5.170659,0 9.333329,-4.16267 9.333329,-9.33334 V 313.33334 c 0,-5.17067 -4.16267,-9.33334 -9.333329,-9.33334 z m 49.777779,0 c -5.17065,0 -9.33333,4.16267 -9.33333,9.33334 v 149.33332 c 0,5.17067 4.16268,9.33334 9.33333,9.33334 5.17065,0 9.33333,-4.16267 9.33333,-9.33334 V 313.33334 c 0,-5.17067 -4.16268,-9.33334 -9.33333,-9.33334 z m 49.77777,0 c -5.17065,0 -9.33333,4.16267 -9.33333,9.33334 v 149.33332 c 0,5.17067 4.16268,9.33334 9.33333,9.33334 C 203.83733,472 208,467.83733 208,462.66666 V 313.33334 C 208,308.16267 203.83733,304 198.66666,304 Z" /> 54 + <path 55 + style="font-style:italic;font-weight:bold;font-size:151.33px;font-family:'Droid Sans Thai';-inkscape-font-specification:'Droid Sans Thai Bold Italic';text-align:center;white-space:pre;fill:#ffffff;stroke-width:24.8853;stroke-linejoin:round;stroke-opacity:0" 56 + d="m 376.56929,472 q -17.82771,0 -31.46068,-5.42608 -13.63295,-5.21741 -22.86141,-14.8174 -9.01873,-9.59999 -13.63297,-22.33043 Q 304,416.69565 304,402.08695 q 0,-18.9913 5.03371,-34.43478 5.24343,-15.44346 14.4719,-27.33912 9.4382,-11.89566 21.81274,-20.03479 12.37453,-8.13914 26.84645,-12.10436 Q 386.84643,304 402.36705,304 q 21.81272,0 37.33333,7.51305 15.52059,7.51302 23.9101,21.70435 Q 472,347.40869 472,367.44346 q 0,9.8087 -2.30712,19.40873 -2.09736,9.39129 -6.29212,17.73913 -4.19476,8.13911 -10.48691,14.39998 -6.0824,6.05217 -14.05242,9.59999 -7.97004,3.54785 -17.61797,3.54785 -8.1798,0 -14.26219,-3.54785 -6.08238,-3.75651 -7.97003,-11.47826 h -1.04869 q -4.40451,6.88696 -10.48689,11.06088 -5.87266,3.96523 -15.73035,3.96523 -11.53557,0 -19.50563,-7.30436 -7.97002,-7.51304 -7.97002,-24.20868 0,-8.34785 2.30711,-16.48698 2.51685,-8.34781 6.92136,-15.65217 4.61422,-7.30433 11.11611,-12.73044 6.50186,-5.63477 14.89137,-8.76521 8.38952,-3.13044 18.24719,-3.13044 11.32584,0 19.2959,1.66957 7.97003,1.66957 14.26218,3.96521 l -10.90637,42.78261 q -1.0487,4.59131 -1.88765,7.93044 -0.83895,3.13044 -0.83895,6.46958 0,3.75651 1.6779,5.42607 1.6779,1.46088 4.19476,1.46088 4.4045,0 8.38951,-2.50436 3.98501,-2.71303 7.13108,-7.30434 3.14607,-4.59131 5.45317,-10.43479 2.30712,-6.05217 3.56556,-12.52173 1.25841,-6.46958 1.25841,-12.93913 0,-13.77391 -5.45317,-23.7913 -5.24345,-10.01741 -16.1498,-15.44348 -10.90638,-5.4261 -27.89514,-5.4261 -12.37453,0 -23.49064,3.54783 -11.11609,3.33913 -20.13482,10.01738 -9.01876,6.46959 -15.73036,15.86088 -6.50185,9.39131 -10.06741,21.28697 -3.56554,11.89566 -3.56554,25.87826 0,14.1913 5.24344,26.29565 5.24346,12.10436 16.77902,19.40868 11.53561,7.09566 30.20227,7.09566 13.00373,0 23.49064,-2.29565 Q 413.2734,448 424.17977,443.82608 v 18.99132 q -9.85767,3.96521 -21.81272,6.46955 Q 390.62173,472 376.56929,472 Z m 0.62921,-58.43478 q 8.17977,0 13.21348,-7.51305 5.24346,-7.72174 8.59926,-20.03478 l 5.87265,-22.53914 q -1.88764,-0.41737 -3.77528,-0.62607 -1.88762,-0.41741 -4.61422,-0.41741 -7.34082,0 -12.79402,3.75653 -5.45317,3.54783 -9.01872,9.3913 -3.56555,5.63477 -5.45319,12.10436 -1.6779,6.26086 -1.6779,11.68695 0,7.93045 2.93634,11.06087 2.93631,3.13044 6.7116,3.13044 z" 57 + id="text63" 58 + aria-label="@" /> 59 + <rect 60 + style="fill:#000000;fill-opacity:1;stroke-width:0;stroke-linejoin:round;stroke-opacity:0;paint-order:markers fill stroke" 61 + id="rect1" 62 + width="512" 63 + height="16" 64 + x="0" 65 + y="248" /> 66 + <rect 67 + style="fill:#000000;fill-opacity:1;stroke-width:0;stroke-linejoin:round;stroke-opacity:0;paint-order:markers fill stroke" 68 + id="rect2" 69 + width="16" 70 + height="512" 71 + x="248" 72 + y="0" /> 14 73 </g> 15 - </svg></svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } } 74 + <style 75 + id="style4">@media (prefers-color-scheme: light) { :root { filter: none; } } 16 76 @media (prefers-color-scheme: dark) { :root { filter: none; } } 17 - </style></svg> 77 + </style> 78 + </svg>
pkg/appview/public/web-app-manifest-192x192.png

This is a binary file and will not be displayed.

pkg/appview/public/web-app-manifest-512x512.png

This is a binary file and will not be displayed.

+52 -17
pkg/appview/server.go
··· 9 9 "log/slog" 10 10 "net" 11 11 "net/http" 12 + "net/url" 12 13 "os" 13 14 "os/signal" 14 15 "strings" 15 16 "syscall" 16 17 "time" 17 18 19 + "github.com/distribution/distribution/v3/registry/api/errcode" 18 20 "github.com/distribution/distribution/v3/registry/handlers" 19 21 "github.com/go-chi/chi/v5" 20 22 chimiddleware "github.com/go-chi/chi/v5/middleware" ··· 239 241 mainRouter.Use(chimiddleware.GetHead) 240 242 mainRouter.Use(routes.CORSMiddleware()) 241 243 242 - // Registry domain redirect middleware 244 + // Domain routing middleware 243 245 if len(cfg.Server.RegistryDomains) > 0 { 244 - mainRouter.Use(RegistryDomainRedirect(cfg.Server.RegistryDomains, cfg.Server.BaseURL)) 245 - slog.Info("Registry domain redirect enabled", 246 + mainRouter.Use(DomainRoutingMiddleware(cfg.Server.RegistryDomains, cfg.Server.BaseURL)) 247 + slog.Info("Domain routing middleware enabled", 246 248 "registry_domains", cfg.Server.RegistryDomains, 247 249 "ui_base_url", cfg.Server.BaseURL) 248 250 } ··· 580 582 ) 581 583 } 582 584 583 - // RegistryDomainRedirect redirects all non-registry requests from registry 584 - // domains to the UI domain. Only /v2 and /v2/* pass through for Docker clients. 585 - // Uses 307 (Temporary Redirect) to preserve POST method/body. 586 - func RegistryDomainRedirect(registryDomains []string, uiBaseURL string) func(http.Handler) http.Handler { 587 - domains := make(map[string]bool, len(registryDomains)) 585 + // DomainRoutingMiddleware enforces three-tier domain routing: 586 + // 587 + // 1. UI domain (BaseURL hostname): serves web UI, auth, and static assets. 588 + // Blocks /v2/* with an OCI UNSUPPORTED error — registry API lives on 589 + // the dedicated registry domain(s). 590 + // 2. Registry domains: allows /v2/* for Docker clients. Redirects everything 591 + // else to the UI domain with 307 Temporary Redirect. 592 + // 3. Unknown domains (CDN origins, IPs, etc.): redirects all requests to the 593 + // UI domain with 307, except /health for load balancer probes. 594 + func DomainRoutingMiddleware(registryDomains []string, uiBaseURL string) func(http.Handler) http.Handler { 595 + regDomains := make(map[string]bool, len(registryDomains)) 588 596 for _, d := range registryDomains { 589 - domains[d] = true 597 + regDomains[d] = true 598 + } 599 + 600 + // Extract UI hostname from BaseURL (e.g., "https://seamark.dev" -> "seamark.dev") 601 + var uiHost string 602 + if parsed, err := url.Parse(uiBaseURL); err == nil { 603 + uiHost = parsed.Hostname() 590 604 } 591 605 606 + primaryReg := primaryRegistryDomain(registryDomains) 607 + 592 608 return func(next http.Handler) http.Handler { 593 609 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 594 610 host := r.Host ··· 596 612 host = host[:idx] 597 613 } 598 614 599 - if domains[host] { 600 - path := r.URL.Path 601 - if path == "/v2" || path == "/v2/" || strings.HasPrefix(path, "/v2/") { 615 + path := r.URL.Path 616 + isV2 := path == "/v2" || path == "/v2/" || strings.HasPrefix(path, "/v2/") 617 + 618 + switch { 619 + case host == uiHost: 620 + // UI domain: block /v2/*, serve everything else 621 + if isV2 { 622 + if err := errcode.ServeJSON(w, errcode.ErrorCodeUnsupported.WithMessage( 623 + fmt.Sprintf("registry API is not available on this domain, use %s", primaryReg), 624 + )); err != nil { 625 + slog.Error("failed to write OCI error response", "error", err) 626 + } 627 + return 628 + } 629 + next.ServeHTTP(w, r) 630 + 631 + case regDomains[host]: 632 + // Registry domain: allow /v2/*, redirect everything else 633 + if isV2 { 602 634 next.ServeHTTP(w, r) 603 635 return 604 636 } 637 + http.Redirect(w, r, uiBaseURL+r.URL.RequestURI(), http.StatusTemporaryRedirect) 605 638 606 - target := uiBaseURL + r.URL.RequestURI() 607 - http.Redirect(w, r, target, http.StatusTemporaryRedirect) 608 - return 639 + default: 640 + // Unknown domain: allow /health, redirect everything else 641 + if path == "/health" { 642 + next.ServeHTTP(w, r) 643 + return 644 + } 645 + http.Redirect(w, r, uiBaseURL+r.URL.RequestURI(), http.StatusTemporaryRedirect) 609 646 } 610 - 611 - next.ServeHTTP(w, r) 612 647 }) 613 648 } 614 649 }