willow.go

  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
 50//goland:noinspection GoUnusedFunction
 51func main() {
 52	flag.Parse()
 53
 54	err := checkConfig()
 55	if err != nil {
 56		log.Fatalln(err)
 57	}
 58
 59	fmt.Println("Opening database at", config.DBConn)
 60
 61	dbConn, err := db.Open(config.DBConn)
 62	if err != nil {
 63		fmt.Println("Error opening database:", err)
 64		os.Exit(1)
 65	}
 66
 67	fmt.Println("Verifying database schema")
 68	err = db.VerifySchema(dbConn)
 69	if err != nil {
 70		fmt.Println("Error verifying database schema:", err)
 71		fmt.Println("Attempting to load schema")
 72		err = db.LoadSchema(dbConn)
 73		if err != nil {
 74			fmt.Println("Error loading schema:", err)
 75			os.Exit(1)
 76		}
 77	}
 78	fmt.Println("Database schema verified")
 79
 80	if len(*flagAddUser) > 0 && len(*flagDeleteUser) == 0 && !*flagListUsers && len(*flagCheckAuthorised) == 0 {
 81		createUser(dbConn, *flagAddUser)
 82		os.Exit(0)
 83	} else if len(*flagAddUser) == 0 && len(*flagDeleteUser) > 0 && !*flagListUsers && len(*flagCheckAuthorised) == 0 {
 84		deleteUser(dbConn, *flagDeleteUser)
 85		os.Exit(0)
 86	} else if len(*flagAddUser) == 0 && len(*flagDeleteUser) == 0 && *flagListUsers && len(*flagCheckAuthorised) == 0 {
 87		listUsers(dbConn)
 88		os.Exit(0)
 89	} else if len(*flagAddUser) == 0 && len(*flagDeleteUser) == 0 && !*flagListUsers && len(*flagCheckAuthorised) > 0 {
 90		checkAuthorised(dbConn, *flagCheckAuthorised)
 91		os.Exit(0)
 92	}
 93
 94	fmt.Println("Starting refresh loop")
 95	go project.RefreshLoop(dbConn, config.FetchInterval, &manualRefresh, &req, &res)
 96
 97	var mutex sync.Mutex
 98
 99	wsHandler := ws.Handler{
100		DbConn:        dbConn,
101		Mutex:         &mutex,
102		Req:           &req,
103		Res:           &res,
104		ManualRefresh: &manualRefresh,
105	}
106
107	mux := http.NewServeMux()
108	mux.HandleFunc("/", wsHandler.RootHandler)
109	mux.HandleFunc("/static", ws.StaticHandler)
110	mux.HandleFunc("/new", wsHandler.NewHandler)
111	mux.HandleFunc("/login", wsHandler.LoginHandler)
112	mux.HandleFunc("/logout", wsHandler.LogoutHandler)
113
114	httpServer := &http.Server{
115		Addr:    config.Server.Listen,
116		Handler: mux,
117	}
118
119	fmt.Println("Starting web server on", config.Server.Listen)
120	if err := httpServer.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
121		fmt.Println("Web server closed")
122		os.Exit(0)
123	} else {
124		fmt.Println(err)
125		os.Exit(1)
126	}
127}
128
129func checkConfig() error {
130	file, err := os.Open(*flagConfig)
131	if err != nil {
132		if os.IsNotExist(err) {
133			file, err = os.Create(*flagConfig)
134			if err != nil {
135				return err
136			}
137			defer file.Close()
138
139			_, err = file.WriteString(`# Path to SQLite database
140DBConn = "willow.sqlite"
141# How often to fetch new releases in seconds
142FetchInterval = 3600
143
144[Server]
145# Address to listen on
146Listen = "127.0.0.1:1313"
147				`)
148			if err != nil {
149				return err
150			}
151
152			fmt.Println("Config file created at", *flagConfig)
153			fmt.Println("Please edit it and restart the server")
154			os.Exit(0)
155		} else {
156			return err
157		}
158	}
159	defer file.Close()
160
161	_, err = toml.DecodeFile(*flagConfig, &config)
162	if err != nil {
163		return err
164	}
165
166	if config.FetchInterval < 10 {
167		fmt.Println("Fetch interval is set to", config.FetchInterval, "seconds, but the minimum is 10, using 10")
168		config.FetchInterval = 10
169	}
170
171	if config.Server.Listen == "" {
172		fmt.Println("No listen address specified, using 127.0.0.1:1313")
173		config.Server.Listen = "127.0.0.1:1313"
174	}
175
176	if config.DBConn == "" {
177		fmt.Println("No SQLite path specified, using \"willow.sqlite\"")
178		config.DBConn = "willow.sqlite"
179	}
180
181	return nil
182}