4fbf3ac966
A task in retry backoff was filtered out before the candidate loop, so it recorded no rejection at all: queued, apparently assignable, and silent. That is the exact shape that made F5 take a live session to diagnose. It now reports "retry backoff until <time>". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92 lines
2.1 KiB
Go
92 lines
2.1 KiB
Go
// orchestra-user manages browser-operator accounts in Orchestra's embedded
|
|
// credential database. Passwords are read from the terminal and hashed inside
|
|
// the database; no reusable hash has to be copied into deployment config.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"orchestra/internal/authn"
|
|
"os"
|
|
"syscall"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "usage:")
|
|
fmt.Fprintln(os.Stderr, " orchestra-user set -data DIR -username NAME")
|
|
fmt.Fprintln(os.Stderr, " orchestra-user list -data DIR")
|
|
}
|
|
|
|
func readPassword(prompt string) (string, error) {
|
|
fmt.Fprint(os.Stderr, prompt)
|
|
value, err := term.ReadPassword(int(syscall.Stdin))
|
|
fmt.Fprintln(os.Stderr)
|
|
return string(value), err
|
|
}
|
|
|
|
func open(data string) *authn.Store {
|
|
if data == "" {
|
|
log.Fatal("-data is required")
|
|
}
|
|
users, err := authn.Open(authn.Path(data))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return users
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
switch os.Args[1] {
|
|
case "set":
|
|
flags := flag.NewFlagSet("set", flag.ExitOnError)
|
|
data := flags.String("data", "", "Orchestra data directory")
|
|
username := flags.String("username", "", "operator username")
|
|
_ = flags.Parse(os.Args[2:])
|
|
password, err := readPassword("New password: ")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
confirmation, err := readPassword("Confirm password: ")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if password != confirmation {
|
|
log.Fatal("passwords do not match")
|
|
}
|
|
users := open(*data)
|
|
defer users.Close()
|
|
user, created, err := users.SetPassword(*username, password)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if created {
|
|
fmt.Printf("created operator %s\n", user.Username)
|
|
} else {
|
|
fmt.Printf("updated password for %s\n", user.Username)
|
|
}
|
|
case "list":
|
|
flags := flag.NewFlagSet("list", flag.ExitOnError)
|
|
data := flags.String("data", "", "Orchestra data directory")
|
|
_ = flags.Parse(os.Args[2:])
|
|
users := open(*data)
|
|
defer users.Close()
|
|
list, err := users.Users()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, user := range list {
|
|
fmt.Println(user.Username)
|
|
}
|
|
default:
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
}
|