package main import ( "bufio" "context" "encoding/json" "flag" "fmt" "log" "net/http" "os" "github.com/autobrr/autobrr/internal/config" "github.com/autobrr/autobrr/internal/database" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/pkg/argon2id" "github.com/autobrr/autobrr/pkg/errors" "golang.org/x/term" _ "modernc.org/sqlite" ) const usage = `usage: autobrrctl --config path create-user Create user change-password Change password for user version Can be run without --config help Show this help message ` var ( version = "dev" commit = "" date = "" owner = "autobrr" repo = "autobrr" ) func init() { flag.Usage = func() { fmt.Fprint(flag.CommandLine.Output(), usage) } } func main() { var configPath string flag.StringVar(&configPath, "config", "", "path to configuration file") flag.Parse() switch cmd := flag.Arg(0); cmd { case "version": fmt.Fprintf(flag.CommandLine.Output(), "Version: %v\nCommit: %v\nBuild: %v\n", version, commit, date) // get the latest release tag from Github resp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)) if err == nil && resp.StatusCode == http.StatusOK { defer resp.Body.Close() var rel struct { TagName string `json:"tag_name"` } if err := json.NewDecoder(resp.Body).Decode(&rel); err == nil { fmt.Fprintf(flag.CommandLine.Output(), "Latest release: %v\n", rel.TagName) } } case "create-user": if configPath == "" { log.Fatal("--config required") } // read config cfg := config.New(configPath, version) // init new logger l := logger.New(cfg.Config) // open database connection db, _ := database.NewDB(cfg.Config, l) if err := db.Open(); err != nil { log.Fatal("could not open db connection") } userRepo := database.NewUserRepo(l, db) username := flag.Arg(1) if username == "" { flag.Usage() os.Exit(1) } password, err := readPassword() if err != nil { log.Fatalf("failed to read password: %v", err) } hashed, err := argon2id.CreateHash(string(password), argon2id.DefaultParams) if err != nil { log.Fatalf("failed to hash password: %v", err) } user := domain.User{ Username: username, Password: hashed, } if err := userRepo.Store(context.Background(), user); err != nil { log.Fatalf("failed to create user: %v", err) } case "change-password": if configPath == "" { log.Fatal("--config required") } // read config cfg := config.New(configPath, version) // init new logger l := logger.New(cfg.Config) // open database connection db, _ := database.NewDB(cfg.Config, l) if err := db.Open(); err != nil { log.Fatal("could not open db connection") } userRepo := database.NewUserRepo(l, db) username := flag.Arg(1) if username == "" { flag.Usage() os.Exit(1) } user, err := userRepo.FindByUsername(context.Background(), username) if err != nil { log.Fatalf("failed to get user: %v", err) } if user == nil { log.Fatalf("failed to get user: %v", err) } password, err := readPassword() if err != nil { log.Fatalf("failed to read password: %v", err) } hashed, err := argon2id.CreateHash(string(password), argon2id.DefaultParams) if err != nil { log.Fatalf("failed to hash password: %v", err) } user.Password = hashed if err := userRepo.Update(context.Background(), *user); err != nil { log.Fatalf("failed to create user: %v", err) } default: flag.Usage() if cmd != "help" { os.Exit(1) } } } func readPassword() ([]byte, error) { var password []byte var err error fd := int(os.Stdin.Fd()) if term.IsTerminal(fd) { fmt.Printf("Password: ") password, err = term.ReadPassword(int(os.Stdin.Fd())) if err != nil { return nil, err } fmt.Printf("\n") } else { //fmt.Fprintf(os.Stderr, "warning: Reading password from stdin.\n") scanner := bufio.NewScanner(os.Stdin) if !scanner.Scan() { if err := scanner.Err(); err != nil { log.Fatalf("failed to read password from stdin: %v", err) } log.Fatalf("failed to read password from stdin: stdin is empty %v", err) } password = scanner.Bytes() if len(password) == 0 { return nil, errors.New("zero length password") } } return password, nil }