Master keys: a down server no longer breaks the device list, 403 for keys outside the API key's scope

masterDevices skips a server that did not answer and reports it as
unreachable; peers are fetched once per server per pass. The tab loads each
key on its own, counts devices from the key when the list is missing (and still
asks before unbinding), drops stale responses, labels an IP from another
server and blocks a double create. masterFor answers 403 instead of 404 for a
key that exists but is outside the API key's servers, so a client does not
mistake it for a deleted one. updateMaster checks every statement in its
transaction.
This commit is contained in:
2026-10-20 20:39:09 +03:00
parent 28ccc17bde
commit 3511b8c6c3
5 changed files with 233 additions and 63 deletions
+9 -5
View File
@@ -186,6 +186,9 @@ type Sub struct {
locksMu sync.Mutex
locks map[int]*sync.Mutex // один проход на сервер за раз
peersMu sync.Mutex
peerCalls map[int]*peersCall // последний GET /api/peers по серверу, см. peersOf
sigMu sync.Mutex
seenSigs map[string]time.Time // подпись → когда забыть
@@ -201,11 +204,12 @@ type Sub struct {
func newSub(a *App) (*Sub, error) {
s := &Sub{
a: a, port: envNum("SUB_PORT"),
useTLS: strings.ToLower(envOr("SUB_TLS", "off")) == "on",
host: strings.TrimSpace(os.Getenv("SUB_HOST")),
auditOn: strings.ToLower(envOr("AUDIT", "on")) != "off",
locks: map[int]*sync.Mutex{},
seenSigs: map[string]time.Time{},
useTLS: strings.ToLower(envOr("SUB_TLS", "off")) == "on",
host: strings.TrimSpace(os.Getenv("SUB_HOST")),
auditOn: strings.ToLower(envOr("AUDIT", "on")) != "off",
locks: map[int]*sync.Mutex{},
peerCalls: map[int]*peersCall{},
seenSigs: map[string]time.Time{},
}
limited := func(what string) func(string) {
return func(ip string) {