package main import ( "bufio" "crypto/sha256" "encoding/hex" "os" "path/filepath" "strings" ) // serversFile is where a runner remembers what it attached to, so appendix E's line needs no flag. func serversFile() (string, error) { dir, err := os.UserConfigDir() if err != nil { return "", err } return filepath.Join(dir, "forge", "servers"), nil } // tokenKey is a hash, because the file names which server a token belongs to and never the token. func tokenKey(tok string) string { sum := sha256.Sum256([]byte(tok)) return hex.EncodeToString(sum[:8]) } // rememberServer records where a token attached, after it worked, so a bad url is never kept. func rememberServer(tok, server string) { path, err := serversFile() if err != nil { return } key := tokenKey(tok) lines := readServers(path) out := make([]string, 0, len(lines)+1) for _, line := range lines { if k, _, ok := strings.Cut(line, " "); !ok || k != key { out = append(out, line) } } out = append(out, key+" "+server) if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return } // A failure here costs the next run a flag, which is not worth failing a build over. os.WriteFile(path, []byte(strings.Join(out, "\n")+"\n"), 0o600) } // recallServer answers where this token attached last time, or nothing. func recallServer(tok string) string { path, err := serversFile() if err != nil { return "" } key := tokenKey(tok) for _, line := range readServers(path) { if k, server, ok := strings.Cut(line, " "); ok && k == key { return server } } return "" } func readServers(path string) []string { f, err := os.Open(path) if err != nil { return nil } defer f.Close() var out []string sc := bufio.NewScanner(f) for sc.Scan() { if line := strings.TrimSpace(sc.Text()); line != "" { out = append(out, line) } } return out }