1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5package main
6
7import (
8 "errors"
9 "fmt"
10 "log"
11 "net/http"
12 "os"
13 "sync"
14
15 "git.sr.ht/~amolith/willow/db"
16 "git.sr.ht/~amolith/willow/project"
17 "git.sr.ht/~amolith/willow/ws"
18
19 "github.com/BurntSushi/toml"
20 flag "github.com/spf13/pflag"
21)
22
23type (
24 Config struct {
25 Server server
26 CSVLocation string
27 DBConn string
28 // TODO: Make cache location configurable
29 // CacheLocation string
30 FetchInterval int
31 }
32
33 server struct {
34 Listen string
35 }
36)
37
38var (
39 flagConfig = flag.StringP("config", "c", "config.toml", "Path to config file")
40 flagAddUser = flag.StringP("add", "a", "", "Username of account to add")
41 flagDeleteUser = flag.StringP("deleteuser", "d", "", "Username of account to delete")
42 flagCheckAuthorised = flag.StringP("validatecredentials", "v", "", "Username of account to check")
43 flagListUsers = flag.BoolP("listusers", "l", false, "List all users")
44 config Config
45 req = make(chan struct{})
46 res = make(chan []project.Project)
47 manualRefresh = make(chan struct{})
48)
49
50func main() {
51 flag.Parse()
52
53 err := checkConfig()
54 if err != nil {
55 log.Fatalln(err)
56 }
57
58 fmt.Println("Opening database at", config.DBConn)
59
60 dbConn, err := db.Open(config.DBConn)
61 if err != nil {
62 fmt.Println("Error opening database:", err)
63 os.Exit(1)
64 }
65
66 fmt.Println("Checking whether database needs initialising")
67 err = db.InitialiseDatabase(dbConn)
68 if err != nil {
69 fmt.Println("Error initialising database:", err)
70 os.Exit(1)
71 }
72 fmt.Println("Checking whether there are pending migrations")
73 err = db.Migrate(dbConn)
74 if err != nil {
75 fmt.Println("Error migrating database schema:", err)
76 os.Exit(1)
77 }
78
79 if len(*flagAddUser) > 0 && len(*flagDeleteUser) == 0 && !*flagListUsers && len(*flagCheckAuthorised) == 0 {
80 createUser(dbConn, *flagAddUser)
81 os.Exit(0)
82 } else if len(*flagAddUser) == 0 && len(*flagDeleteUser) > 0 && !*flagListUsers && len(*flagCheckAuthorised) == 0 {
83 deleteUser(dbConn, *flagDeleteUser)
84 os.Exit(0)
85 } else if len(*flagAddUser) == 0 && len(*flagDeleteUser) == 0 && *flagListUsers && len(*flagCheckAuthorised) == 0 {
86 listUsers(dbConn)
87 os.Exit(0)
88 } else if len(*flagAddUser) == 0 && len(*flagDeleteUser) == 0 && !*flagListUsers && len(*flagCheckAuthorised) > 0 {
89 checkAuthorised(dbConn, *flagCheckAuthorised)
90 os.Exit(0)
91 }
92
93 fmt.Println("Starting refresh loop")
94 go project.RefreshLoop(dbConn, config.FetchInterval, &manualRefresh, &req, &res)
95
96 var mutex sync.Mutex
97
98 wsHandler := ws.Handler{
99 DbConn: dbConn,
100 Mutex: &mutex,
101 Req: &req,
102 Res: &res,
103 ManualRefresh: &manualRefresh,
104 }
105
106 mux := http.NewServeMux()
107 mux.HandleFunc("/static/", ws.StaticHandler)
108 mux.HandleFunc("/new", wsHandler.NewHandler)
109 mux.HandleFunc("/login", wsHandler.LoginHandler)
110 mux.HandleFunc("/logout", wsHandler.LogoutHandler)
111 mux.HandleFunc("/", wsHandler.RootHandler)
112
113 httpServer := &http.Server{
114 Addr: config.Server.Listen,
115 Handler: mux,
116 }
117
118 fmt.Println("Starting web server on", config.Server.Listen)
119 if err := httpServer.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
120 fmt.Println("Web server closed")
121 os.Exit(0)
122 } else {
123 fmt.Println(err)
124 os.Exit(1)
125 }
126}
127
128func checkConfig() error {
129 file, err := os.Open(*flagConfig)
130 if err != nil {
131 if os.IsNotExist(err) {
132 file, err = os.Create(*flagConfig)
133 if err != nil {
134 return err
135 }
136 defer file.Close()
137
138 _, err = file.WriteString(`# Path to SQLite database
139DBConn = "willow.sqlite"
140# How often to fetch new releases in seconds
141FetchInterval = 3600
142
143[Server]
144# Address to listen on
145Listen = "127.0.0.1:1313"
146 `)
147 if err != nil {
148 return err
149 }
150
151 fmt.Println("Config file created at", *flagConfig)
152 fmt.Println("Please edit it and restart the server")
153 os.Exit(0)
154 } else {
155 return err
156 }
157 }
158 defer file.Close()
159
160 _, err = toml.DecodeFile(*flagConfig, &config)
161 if err != nil {
162 return err
163 }
164
165 if config.FetchInterval < 10 {
166 fmt.Println("Fetch interval is set to", config.FetchInterval, "seconds, but the minimum is 10, using 10")
167 config.FetchInterval = 10
168 }
169
170 if config.Server.Listen == "" {
171 fmt.Println("No listen address specified, using 127.0.0.1:1313")
172 config.Server.Listen = "127.0.0.1:1313"
173 }
174
175 if config.DBConn == "" {
176 fmt.Println("No SQLite path specified, using \"willow.sqlite\"")
177 config.DBConn = "willow.sqlite"
178 }
179
180 return nil
181}