diff --git a/Dockerfile b/Dockerfile index d55f059..0ba545a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,11 +11,12 @@ COPY ./webui /ui RUN npm install RUN npm run build -FROM alpine:3.8 +FROM alpine:3.8 as install-musl RUN apk add --no-cache sqlite -COPY /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 -RUN apk remove sqlite + +FROM alpine:3.8 +COPY --from=install-musl /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 COPY --from=build-server /server/lucifer-server /usr/local/bin/ COPY --from=build-ui /ui/build /usr/local/share/lucifer-ui -CMD ["/usr/local/bin/lucifer-server"] \ No newline at end of file +CMD ["/usr/local/bin/lucifer-server", "-uidir", "/usr/local/share/lucifer-ui"] \ No newline at end of file diff --git a/cmd/lucifer-server/main.go b/cmd/lucifer-server/main.go index 2f4124d..89cde78 100644 --- a/cmd/lucifer-server/main.go +++ b/cmd/lucifer-server/main.go @@ -5,31 +5,42 @@ import ( "crypto/rand" "database/sql" "encoding/hex" + "flag" "fmt" "log" "net/http" + "os" + "os/signal" + "path" + "syscall" "time" - "git.aiterp.net/lucifer/lucifer/light" - "git.aiterp.net/lucifer/lucifer/models" - - "github.com/gorilla/mux" - "git.aiterp.net/lucifer/lucifer/controllers" "git.aiterp.net/lucifer/lucifer/database/sqlite" "git.aiterp.net/lucifer/lucifer/internal/config" + "git.aiterp.net/lucifer/lucifer/light" "git.aiterp.net/lucifer/lucifer/middlewares" + "git.aiterp.net/lucifer/lucifer/models" + "github.com/gorilla/mux" _ "git.aiterp.net/lucifer/lucifer/light/hue" ) func main() { - // Setup + // Flag Variables + var uiDir string + + // Parse Flags + flag.StringVar(&uiDir, "uidir", "/var/www/", "UI directory") + flag.Parse() + + // Configuration conf, err := config.Load("./config.yaml", "/etc/lucifer/lucifer.yaml") if err != nil { log.Fatalln("Failed to load configuration:", err) } + // Database err = sqlite.Initialize(conf.DB.FileName) if err != nil { log.Fatalln("Failed to set up database:", err) @@ -58,8 +69,57 @@ func main() { // Background Tasks go lightService.SyncLoop(context.TODO()) - // TODO: Listen in another goroutine and have SIGINT/SIGTERM handlers with graceful shutdown. - http.ListenAndServe(conf.Server.Address, router) + // Server UI in production + stat, err := os.Stat(uiDir) + if err != nil || !stat.IsDir() { + log.Println("ui directory not found, skipping UI routes") + } else { + // Static files + router.PathPrefix("/static/").Handler(http.FileServer(http.Dir(uiDir))) + router.PathPrefix("/favicon.ico").Handler(http.FileServer(http.Dir(uiDir))) + router.PathPrefix("/manifest.json").Handler(http.FileServer(http.Dir(uiDir))) + router.PathPrefix("/index.html").Handler(http.FileServer(http.Dir(uiDir))) + + // Serve index on any other path + router.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, path.Join(uiDir, "index.html")) + }) + } + + // Setup webserver + server := http.Server{Addr: conf.Server.Address, Handler: router} + webserverErrCh := make(chan error) + go func() { + log.Println("Listening on", conf.Server.Address) + webserverErrCh <- server.ListenAndServe() + }() + + // Setup quit signal listener + signalCh := make(chan os.Signal) + signal.Notify(signalCh, os.Interrupt, os.Kill, syscall.SIGTERM) + + // Run until interrupt or server death + select { + case signal := <-signalCh: + { + log.Println("Stopping due to signal:", signal) + + timeout, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + err := server.Shutdown(timeout) + if err != nil { + log.Println("Warning: graceful shutdown timed out.") + } + + os.Exit(0) + } + case err := <-webserverErrCh: + { + log.Println("Listening failed:", err) + os.Exit(1) + } + } } func setupAdmin(users models.UserRepository, groups models.GroupRepository) { diff --git a/middlewares/session.go b/middlewares/session.go index 4c2e743..280a575 100644 --- a/middlewares/session.go +++ b/middlewares/session.go @@ -23,7 +23,7 @@ func Session(sessions models.SessionRepository, users models.UserRepository) mux } redirectFailure := func(next http.Handler, w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/api/user/") { + if !strings.HasPrefix(r.URL.Path, "/api/") || strings.HasPrefix(r.URL.Path, "/api/user/") { next.ServeHTTP(w, r) } else { httperr.Respond(w, httperr.ErrLoginRequired)