Choose the hub: Settings' choice, else the install's channel's; state kept per hub
CI / build (push) Successful in 18s
CI / build (push) Successful in 18s
The client's Settings > T5DE chooses the hub from the next start on (POST /hub/server, kept in server.json); without a choice, -channel (which the client passes) decides: staging for nightly and dev, T5DE's hub for releases. $T5DE_HUB still wins. Each hub's sign-in, product IDs and products are kept in their own folder (T5DE's hub in the data folder as before, others in hubs/<host>), so a hub never gets another's token and product IDs from two hubs never clash; a sign-in saved for another hub is ignored. Signing in always goes to this run's hub. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8daf91ede3
commit
8ae45fc07a
6 files changed
+292
-53
No files matched your search
@@ -0,0 +1,114 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// T5DE's hubs.
|
||||
const (
|
||||
Production = "https://hub.t5de.dev"
|
||||
Staging = "https://hub.staging.t5de.dev"
|
||||
)
|
||||
|
||||
// ChannelDefault is the hub an install uses unless the person chooses one: nightlies and builds outside an
|
||||
// install (channel "nightly", "dev") use staging, releases T5DE's hub.
|
||||
func ChannelDefault(channel string) string {
|
||||
if channel == "nightly" || channel == "dev" {
|
||||
return Staging
|
||||
}
|
||||
return Production
|
||||
}
|
||||
|
||||
// Server is the hub for this run: env ($T5DE_HUB) if set, else the person's choice (the client's Settings, kept
|
||||
// in choicePath), else the channel's.
|
||||
func Server(choicePath, channel, env string) string {
|
||||
if env != "" {
|
||||
return strings.TrimSuffix(env, "/")
|
||||
}
|
||||
if c := LoadChoice(choicePath); c != "" {
|
||||
return c
|
||||
}
|
||||
return ChannelDefault(channel)
|
||||
}
|
||||
|
||||
// LoadChoice is the hub the person chose, "" for the channel's.
|
||||
func LoadChoice(path string) string {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var c struct {
|
||||
Hub string `json:"hub"`
|
||||
}
|
||||
if json.Unmarshal(b, &c) != nil {
|
||||
return ""
|
||||
}
|
||||
hub, err := CheckHub(c.Hub)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return hub
|
||||
}
|
||||
|
||||
// SaveChoice remembers the hub the person chose ("" for the channel's).
|
||||
func SaveChoice(path, hub string) error {
|
||||
if hub == "" {
|
||||
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b, _ := json.Marshal(map[string]string{"hub": hub})
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp := path + ".tmp"
|
||||
if err := os.WriteFile(tmp, b, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, path)
|
||||
}
|
||||
|
||||
// CheckHub is a hub's address without a trailing slash, if it's https (or this computer's, for development).
|
||||
func CheckHub(s string) (string, error) {
|
||||
s = strings.TrimSuffix(strings.TrimSpace(s), "/")
|
||||
u, err := url.Parse(s)
|
||||
if err != nil || u.Host == "" || u.RawQuery != "" || u.Fragment != "" {
|
||||
return "", fmt.Errorf("not a hub address: %q", s)
|
||||
}
|
||||
local := u.Hostname() == "localhost" || u.Hostname() == "127.0.0.1"
|
||||
if u.Scheme != "https" && !(u.Scheme == "http" && local) {
|
||||
return "", errors.New("the hub needs an https address")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// StateDir is where the proxy keeps what belongs to a hub (its sign-in, product IDs, products): the data folder
|
||||
// itself for T5DE's hub, hubs/<name> in it for any other, so a hub never gets another's sign-in and product IDs
|
||||
// from two hubs never clash.
|
||||
func StateDir(data, hub string) string {
|
||||
if hub == Production {
|
||||
return data
|
||||
}
|
||||
u, err := url.Parse(hub)
|
||||
name := hub
|
||||
if err == nil {
|
||||
name = u.Host + u.Path
|
||||
}
|
||||
name = strings.Map(func(r rune) rune {
|
||||
if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '.' || r == '-' {
|
||||
return r
|
||||
}
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
return r + 'a' - 'A'
|
||||
}
|
||||
return '_'
|
||||
}, name)
|
||||
return filepath.Join(data, "hubs", name)
|
||||
}
|
||||
Reference in new issue
Block a user