source: code/trunk/cmd/suikactl/main.go@ 820

Last change on this file since 820 was 810, checked in by koizumi.aoi, 2 years ago

Add a 'version' subcommand to suikactl, show version on suika daemon
startup.

Signed-off-by: Aoi K <koizumi.aoi@…>

File size: 3.2 KB
RevLine 
[801]1package main
2
3import (
4 "bufio"
5 "context"
6 "flag"
7 "fmt"
8 "io"
9 "log"
10 "os"
11
[807]12 "marisa.chaotic.ninja/suika"
13 "marisa.chaotic.ninja/suika/config"
[801]14 "golang.org/x/crypto/bcrypt"
15 "golang.org/x/crypto/ssh/terminal"
16)
17
[804]18const usage = `usage: suikactl [-config path] <action> [options...]
[801]19
20 create-user <username> [-admin] Create a new user
21 change-password <username> Change password for a user
22 help Show this help message
23`
24
25func init() {
26 flag.Usage = func() {
27 fmt.Fprintf(flag.CommandLine.Output(), usage)
28 }
29}
30
31func main() {
32 var configPath string
33 flag.StringVar(&configPath, "config", "", "path to configuration file")
34 flag.Parse()
35
36 var cfg *config.Server
37 if configPath != "" {
38 var err error
39 cfg, err = config.Load(configPath)
40 if err != nil {
41 log.Fatalf("failed to load config file: %v", err)
42 }
43 } else {
44 cfg = config.Defaults()
45 }
46
[804]47 db, err := suika.OpenDB(cfg.SQLDriver, cfg.SQLSource)
[801]48 if err != nil {
49 log.Fatalf("failed to open database: %v", err)
50 }
51
52 ctx := context.Background()
53
54 switch cmd := flag.Arg(0); cmd {
55 case "create-user":
56 username := flag.Arg(1)
57 if username == "" {
58 flag.Usage()
59 os.Exit(1)
60 }
61
62 fs := flag.NewFlagSet("", flag.ExitOnError)
63 admin := fs.Bool("admin", false, "make the new user admin")
64 fs.Parse(flag.Args()[2:])
65
66 password, err := readPassword()
67 if err != nil {
68 log.Fatalf("failed to read password: %v", err)
69 }
70
71 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
72 if err != nil {
73 log.Fatalf("failed to hash password: %v", err)
74 }
75
[804]76 user := suika.User{
[801]77 Username: username,
78 Password: string(hashed),
79 Admin: *admin,
80 }
81 if err := db.StoreUser(ctx, &user); err != nil {
82 log.Fatalf("failed to create user: %v", err)
83 }
84 case "change-password":
85 username := flag.Arg(1)
86 if username == "" {
87 flag.Usage()
88 os.Exit(1)
89 }
90
91 user, err := db.GetUser(ctx, username)
92 if err != nil {
93 log.Fatalf("failed to get user: %v", err)
94 }
95
96 password, err := readPassword()
97 if err != nil {
98 log.Fatalf("failed to read password: %v", err)
99 }
100
101 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
102 if err != nil {
103 log.Fatalf("failed to hash password: %v", err)
104 }
105
106 user.Password = string(hashed)
107 if err := db.StoreUser(ctx, user); err != nil {
108 log.Fatalf("failed to update password: %v", err)
109 }
[810]110 case "version":
111 fmt.Printf("%v\n", suika.FullVersion())
[801]112 default:
113 flag.Usage()
114 if cmd != "help" {
115 os.Exit(1)
116 }
117 }
118}
119
120func readPassword() ([]byte, error) {
121 var password []byte
122 var err error
123 fd := int(os.Stdin.Fd())
124
125 if terminal.IsTerminal(fd) {
126 fmt.Printf("Password: ")
127 password, err = terminal.ReadPassword(int(os.Stdin.Fd()))
128 if err != nil {
129 return nil, err
130 }
131 fmt.Printf("\n")
132 } else {
133 fmt.Fprintf(os.Stderr, "Warning: Reading password from stdin.\n")
134 // TODO: the buffering messes up repeated calls to readPassword
135 scanner := bufio.NewScanner(os.Stdin)
136 if !scanner.Scan() {
137 if err := scanner.Err(); err != nil {
138 return nil, err
139 }
140 return nil, io.ErrUnexpectedEOF
141 }
142 password = scanner.Bytes()
143
144 if len(password) == 0 {
145 return nil, fmt.Errorf("zero length password")
146 }
147 }
148
149 return password, nil
150}
Note: See TracBrowser for help on using the repository browser.