89dfb5e8a0602139b83b4d6d1f981710e1a865b2
max
  Mon Sep 21 05:53:28 2026 -0700
geoMirror: notify peers over https, and drop the hardcoded genome-euro from the invalid-apiKey message, refs #38323

geoMirrorNotifyOtherNodes built its URL as http://.  genome-euro and
genome-asia redirect http to https, and netSlurpUrl does not follow
redirects, so the api key sync request never reached the peer CGI --
and the response is discarded, so nothing said so.  Confirmed with
lib/tests/fetchUrlTest, which uses the same call: http gives back a
bare 302, https gives back the cartJson reply.  https also keeps the
key off the wire in the clear on its way to Germany and Japan.

The geo mirror menu links now use https too.

botDelay's "Invalid apiKey" message pointed at genome-euro no matter
which server the user was on, which was wrong everywhere except euro.
Both branches now build the link from the current server.

diff --git src/hg/lib/botDelay.c src/hg/lib/botDelay.c
index 93f62c54339..937fb3ecb62 100644
--- src/hg/lib/botDelay.c
+++ src/hg/lib/botDelay.c
@@ -219,47 +219,53 @@
         // hgsid, and only if none of these is available, on IP address. Also, check
         // apiKey and cookieId if they are valid, check hgsid if the string looks OK.
         char *apiKey = cgiOptionalString("apiKey");
         if (apiKey)
             {
             // Here we do a mysql query before the bottleneck is complete. 
             // And this is better than handling the request without bottleneck
             // The connection is closed right away, so if the bottleneck leads to a long sleep, it won't tie up
             // the MariaDB server. The cost of opening a connection is less than 1msec.
             struct sqlConnection *conn = hConnectCentralNoCache();
             char *userName = hubSpaceUserNameForApiKey(conn, apiKey);
             sqlDisconnect(&conn);
 
             if (userName)
                 safef(botCheckString, 256, "apiKey%s %f", apiKey, fraction);
-            else if (cfgOptionBooleanDefault("syncHubApiKeys", FALSE))
+            else
+                {
+                // Point at this server, whichever one it is, rather than at a hardcoded
+                // host: the key has to be created where it is used, unless the mirrors
+                // are syncing keys between themselves.
+                char keyUrl[1024];
+                safef(keyUrl, sizeof(keyUrl), "http%s://%s/cgi-bin/hgHubConnect#hubDeveloper",
+                        cgiAppendSForHttps(), cgiServerNamePort());
+                if (cfgOptionBooleanDefault("syncHubApiKeys", FALSE))
                     // hgHubConnect copies a new or revoked key to the other geo mirror nodes,
                     // so a key made on any of them is valid here
                     hUserAbort("Invalid apiKey provided on URL. "
                             "Make sure that the apiKey is valid, "
-                        "check https://genome.ucsc.edu/cgi-bin/hgHubConnect#hubDeveloper to create one "
-                        "or check this key. An apiKey created on any UCSC genome browser mirror "
-                        "(genome.ucsc.edu, genome-euro.ucsc.edu, genome-asia.ucsc.edu) works on all of "
-                        "them. If you have problems with the apiKey, contact us.");
+                            "check %s to create one or check this key. An apiKey created on any "
+                            "UCSC genome browser mirror works on all of them. "
+                            "If you have problems with the apiKey, contact us.", keyUrl);
                 else
                     hUserAbort("Invalid apiKey provided on URL. "
                             "Make sure that the apiKey is valid, "
-                        "check https://genome-euro.ucsc.edu/cgi-bin/hgHubConnect#hubDeveloper to create one "
-                        "or check this key. Note that an apiKey for genome-euro must be created on "
-                        "https://genome-euro.ucsc.edu/cgi-bin/hgHubConnect and the same for genome-asia or "
-                        "other mirrors, apiKeys are server-specific. If you have problems with the apiKey, "
-                        "contact us.");
+                            "check %s to create one or check this key. Note that apiKeys are "
+                            "server-specific, so the key must be created on the same server where "
+                            "it is used. If you have problems with the apiKey, contact us.", keyUrl);
+                }
             }
         else
             {
             if (isValidHguid(cookieUserId))
                 safef(botCheckString, 256, "uid%s %f", cookieUserId, fraction);
             else
                 {
                 // The following happens very rarely on sites like our RR that use the cloudflare captcha,
                 // as all requests (except hgLogin, hgRenderTracks) should come in with a cookie user ID
                 char *hgsid = cgiOptionalString("hgsid");
                 // For now, we do not check the hgsid against the MariaDb table, only check if the string looks OK
                 if (hgsid && isValidHgsidForEarlyBotCheck(hgsid))
                     safef(botCheckString, 256, "sid%s %f", hgsid, fraction);
                 else
                     {