Skip to content

Commit be4066b

Browse files
committed
Add CLI command
1 parent ea1f125 commit be4066b

8 files changed

Lines changed: 219 additions & 3 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ main
3131

3232
# OS X generated file
3333
.DS_Store
34+
# Added by goreleaser init:
35+
dist/

.goreleaser.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# This is an example .goreleaser.yml file with some sensible defaults.
2+
# Make sure to check the documentation at https://goreleaser.com
3+
4+
# The lines below are called `modelines`. See `:help modeline`
5+
# Feel free to remove those if you don't want/need to use them.
6+
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
7+
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
8+
9+
version: 2
10+
11+
before:
12+
hooks:
13+
# You may remove this if you don't use go modules.
14+
- go mod tidy
15+
# you may remove this if you don't need go generate
16+
- go generate ./...
17+
18+
builds:
19+
- env:
20+
- CGO_ENABLED=1
21+
main: ./cmd/sqliteadmin
22+
goos:
23+
- linux
24+
- windows
25+
- darwin
26+
27+
archives:
28+
- format: tar.gz
29+
# this name template makes the OS and Arch compatible with the results of `uname`.
30+
name_template: >-
31+
{{ .ProjectName }}_
32+
{{- title .Os }}_
33+
{{- if eq .Arch "amd64" }}x86_64
34+
{{- else if eq .Arch "386" }}i386
35+
{{- else }}{{ .Arch }}{{ end }}
36+
{{- if .Arm }}v{{ .Arm }}{{ end }}
37+
# use zip for windows archives
38+
format_overrides:
39+
- goos: windows
40+
format: zip
41+
42+
changelog:
43+
sort: asc
44+
filters:
45+
exclude:
46+
- "^docs:"
47+
- "^test:"
48+
49+
release:
50+
footer: >-
51+
52+
---
53+
54+
Released by [GoReleaser](https://github.com/goreleaser/goreleaser).

Makefile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,3 @@ build:
4747
run: build
4848
/tmp/bin/${binary_name}
4949

50-
## run/live: run the application with reloading on file changes
51-
# .PHONY: run/live
52-
# run/live:

cmd/sqliteadmin/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package main
2+
3+
func main() {
4+
Execute()
5+
}

cmd/sqliteadmin/root.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
var rootCmd = &cobra.Command{
10+
Use: "sqliteadmin",
11+
Short: "A web-based SQLite database management tool",
12+
Long: `sqliteadmin is a web-based SQLite database management tool that allows you to view and manage your SQLite database through a web interface (https://sqliteadmin.dev).`,
13+
}
14+
15+
func Execute() {
16+
if err := rootCmd.Execute(); err != nil {
17+
log.Fatalln(err)
18+
}
19+
}

cmd/sqliteadmin/serve.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
"log/slog"
9+
"net/http"
10+
"os"
11+
"os/signal"
12+
"syscall"
13+
"time"
14+
15+
"github.com/go-chi/chi/v5"
16+
"github.com/go-chi/chi/v5/middleware"
17+
"github.com/go-chi/cors"
18+
"github.com/joelseq/sqliteadmin-go"
19+
_ "github.com/mattn/go-sqlite3"
20+
"github.com/spf13/cobra"
21+
)
22+
23+
var port uint
24+
25+
func init() {
26+
serveCmd.Flags().UintVarP(&port, "port", "p", 8080, "Port to run server on")
27+
rootCmd.AddCommand(serveCmd)
28+
}
29+
30+
var serveCmd = &cobra.Command{
31+
Use: "serve [DB_PATH]",
32+
Short: "Spin up an HTTP server to serve requests to the SQLiteAdmin UI",
33+
Args: cobra.MinimumNArgs(1),
34+
Run: func(cmd *cobra.Command, args []string) {
35+
dbPath := args[0]
36+
username := os.Getenv("SQLITEADMIN_USERNAME")
37+
password := os.Getenv("SQLITEADMIN_PASSWORD")
38+
39+
r := getRouter(dbPath, username, password)
40+
41+
addr := fmt.Sprintf(":%d", port)
42+
43+
// Create a done channel to signal when the shutdown is complete
44+
done := make(chan bool, 1)
45+
46+
httpServer := newHTTPServer(addr, r)
47+
48+
// Run graceful shutdown in a separate goroutine
49+
go gracefulShutdown(httpServer, done)
50+
51+
err := httpServer.ListenAndServe()
52+
if err != nil && err != http.ErrServerClosed {
53+
panic(fmt.Sprintf("http server error: %s", err))
54+
}
55+
56+
// Wait for the graceful shutdown to complete
57+
<-done
58+
log.Println("Graceful shutdown complete.")
59+
},
60+
}
61+
62+
func newHTTPServer(addr string, mux *chi.Mux) *http.Server {
63+
return &http.Server{
64+
Addr: addr,
65+
Handler: mux,
66+
IdleTimeout: time.Minute,
67+
ReadTimeout: 10 * time.Second,
68+
WriteTimeout: 30 * time.Second,
69+
}
70+
}
71+
72+
func getRouter(dbPath, username, password string) *chi.Mux {
73+
db, err := sql.Open("sqlite3", dbPath)
74+
if err != nil {
75+
log.Fatalf("Error opening database: %v", err)
76+
}
77+
78+
logger := slog.Default()
79+
80+
// Setup the handler for SQLiteAdmin
81+
config := sqliteadmin.Config{
82+
DB: db,
83+
Username: username,
84+
Password: password,
85+
Logger: logger,
86+
}
87+
sh := sqliteadmin.NewHandler(config)
88+
89+
r := chi.NewRouter()
90+
r.Use(middleware.Logger)
91+
r.Use(cors.Handler(cors.Options{
92+
AllowedOrigins: []string{"https://*", "http://*"},
93+
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
94+
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
95+
AllowCredentials: true,
96+
MaxAge: 300,
97+
}))
98+
r.Post("/", sh.HandlePost)
99+
100+
return r
101+
}
102+
103+
func gracefulShutdown(apiServer *http.Server, done chan bool) {
104+
// Create context that listens for the interrupt signal from the OS.
105+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
106+
defer stop()
107+
108+
// Listen for the interrupt signal.
109+
<-ctx.Done()
110+
111+
log.Println("shutting down gracefully, press Ctrl+C again to force")
112+
113+
// The context is used to inform the server it has 5 seconds to finish
114+
// the request it is currently handling
115+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
116+
defer cancel()
117+
if err := apiServer.Shutdown(ctx); err != nil {
118+
log.Printf("Server forced to shutdown with error: %v", err)
119+
}
120+
121+
log.Println("Server exiting")
122+
123+
// Notify the main goroutine that the shutdown is complete
124+
done <- true
125+
}

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ require (
88
github.com/mattn/go-sqlite3 v1.14.24
99
github.com/mitchellh/mapstructure v1.5.0
1010
github.com/rs/cors v1.11.1
11+
github.com/spf13/cobra v1.9.1
12+
)
13+
14+
require (
15+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
16+
github.com/spf13/pflag v1.0.6 // indirect
1117
)
1218

1319
require (

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
12
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
23
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
45
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
56
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
67
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
8+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
9+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
710
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
811
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
912
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -12,6 +15,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
1215
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1316
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
1417
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
18+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
19+
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
20+
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
21+
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
22+
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1523
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1624
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1725
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

0 commit comments

Comments
 (0)