detached d6ab22b

Improve Server Configuration and Error Handling

- Add field verification before client command execution (ping and push)
- Fix method naming in common inputs
- Remove redundant types and variables
- Update App server initialization and URL generation
commit
d6ab22bc2bcebca69b7bf1cfbd0311912647d809
author
bnema · · 2023-10-25 17:26 UTC
parent
2846d20
signature
unsigned
11 files · +104 -78
M internal/cli/cmd/ping.go +1 -0
@@ -11,6 +11,7 @@ import (
1111 )
1212
1313 func NewPingCommand(a *cli.App) *cobra.Command {
14+ handler.FieldCheck(a)
1415 return &cobra.Command{
1516 Use: "ping",
1617 Short: "Send a ping request to the backend",
@@ -11,6 +11,7 @@ import (
11 )11 )
12 12
13 func NewPingCommand(a *cli.App) *cobra.Command {13 func NewPingCommand(a *cli.App) *cobra.Command {
14+ handler.FieldCheck(a)
14 return &cobra.Command{15 return &cobra.Command{
15 Use: "ping",16 Use: "ping",
16 Short: "Send a ping request to the backend",17 Short: "Send a ping request to the backend",
M internal/cli/cmd/push.go +2 -0
@@ -28,6 +28,8 @@ func NewPushCommand(a *cli.App) *cobra.Command {
2828 var port string
2929 var targetDomain string
3030
31+ handler.FieldCheck(a)
32+
3133 pushCmd := &cobra.Command{
3234 Use: "push [image:tag]",
3335 Short: "Push an image to the server, if no tag is specified, latest is used",
@@ -28,6 +28,8 @@ func NewPushCommand(a *cli.App) *cobra.Command {
28 var port string28 var port string
29 var targetDomain string29 var targetDomain string
30 30
31+ handler.FieldCheck(a)
32+
31 pushCmd := &cobra.Command{33 pushCmd := &cobra.Command{
32 Use: "push [image:tag]",34 Use: "push [image:tag]",
33 Short: "Push an image to the server, if no tag is specified, latest is used",35 Short: "Push an image to the server, if no tag is specified, latest is used",
M internal/cli/cmd/serve.go +7 -4
@@ -10,8 +10,8 @@ import (
1010
1111 // NewServeCommand creates a new serve command
1212 func NewServeCommand(a *server.App) *cobra.Command {
13- var port string
1413 defaultport := "1323"
14+ var port string
1515
1616 // if no flags -p or --port are specified, the default port is used
1717 serveCmd := &cobra.Command{
@@ -20,12 +20,15 @@ func NewServeCommand(a *server.App) *cobra.Command {
2020 Run: func(cmd *cobra.Command, args []string) {
2121 // handler.StartServer will use the value of port, which will be the default
2222 // value if no flag is specified
23- handler.StartServer(a, port)
23+ a.Config.Http.Port = port
24+ err := handler.StartServer(a, port)
25+ if err != nil {
26+ panic(err)
27+ }
2428 },
2529 }
2630
2731 // Attach the flag to serveCmd and store its value in the variable port
28- serveCmd.Flags().StringVarP(&port, "port", "p", defaultport, "Port to listen on")
29-
32+ serveCmd.Flags().StringVarP(&port, "port", "p", defaultport, "port to listen on")
3033 return serveCmd
3134 }
@@ -10,8 +10,8 @@ import (
10 10
11 // NewServeCommand creates a new serve command11 // NewServeCommand creates a new serve command
12 func NewServeCommand(a *server.App) *cobra.Command {12 func NewServeCommand(a *server.App) *cobra.Command {
13- var port string
14 defaultport := "1323"13 defaultport := "1323"
14+ var port string
15 15
16 // if no flags -p or --port are specified, the default port is used16 // if no flags -p or --port are specified, the default port is used
17 serveCmd := &cobra.Command{17 serveCmd := &cobra.Command{
@@ -20,12 +20,15 @@ func NewServeCommand(a *server.App) *cobra.Command {
20 Run: func(cmd *cobra.Command, args []string) {20 Run: func(cmd *cobra.Command, args []string) {
21 // handler.StartServer will use the value of port, which will be the default21 // handler.StartServer will use the value of port, which will be the default
22 // value if no flag is specified22 // value if no flag is specified
23- handler.StartServer(a, port)23+ a.Config.Http.Port = port
24+ err := handler.StartServer(a, port)
25+ if err != nil {
26+ panic(err)
27+ }
24 },28 },
25 }29 }
26 30
27 // Attach the flag to serveCmd and store its value in the variable port31 // Attach the flag to serveCmd and store its value in the variable port
28- serveCmd.Flags().StringVarP(&port, "port", "p", defaultport, "Port to listen on")32+ serveCmd.Flags().StringVarP(&port, "port", "p", defaultport, "port to listen on")
29-
30 return serveCmd33 return serveCmd
31 }34 }
A internal/cli/handler/fieldcheck.go +50 -0
@@ -0,0 +1,50 @@
1+package handler
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/cli"
7+ "github.com/bnema/gordon/internal/common"
8+)
9+
10+// config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
11+// config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
12+// check in the config.yml if BackendURL and Token are set
13+// if not, prompt the user to enter them
14+
15+func checkIfBackendURLisSet(a *cli.App) (string, error) {
16+ if a.Config.Http.BackendURL == "" {
17+ fmt.Println("BackendURL is not set in config.yml")
18+ a.Config.Http.BackendURL = common.ReadUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
19+ return a.Config.Http.BackendURL, nil
20+ }
21+ return a.Config.Http.BackendURL, nil
22+}
23+
24+func checkIfTokenIsSet(a *cli.App) (string, error) {
25+ if a.Config.General.Token == "" {
26+ fmt.Println("Token is not set in config.yml")
27+ a.Config.General.Token = common.ReadUserInput("Enter the token (check your backend config.yml):")
28+ return a.Config.General.Token, nil
29+ }
30+ return a.Config.General.Token, nil
31+}
32+
33+func FieldCheck(a *cli.App) error {
34+ _, err := checkIfBackendURLisSet(a)
35+ if err != nil {
36+ return err
37+ }
38+ _, err = checkIfTokenIsSet(a)
39+ if err != nil {
40+ return err
41+ }
42+
43+ // save config
44+ err = a.Config.SaveConfig()
45+ if err != nil {
46+ return err
47+ }
48+
49+ return nil
50+}
@@ -0,0 +1,50 @@
1+package handler
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/cli"
7+ "github.com/bnema/gordon/internal/common"
8+)
9+
10+// config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
11+// config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
12+// check in the config.yml if BackendURL and Token are set
13+// if not, prompt the user to enter them
14+
15+func checkIfBackendURLisSet(a *cli.App) (string, error) {
16+ if a.Config.Http.BackendURL == "" {
17+ fmt.Println("BackendURL is not set in config.yml")
18+ a.Config.Http.BackendURL = common.ReadUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
19+ return a.Config.Http.BackendURL, nil
20+ }
21+ return a.Config.Http.BackendURL, nil
22+}
23+
24+func checkIfTokenIsSet(a *cli.App) (string, error) {
25+ if a.Config.General.Token == "" {
26+ fmt.Println("Token is not set in config.yml")
27+ a.Config.General.Token = common.ReadUserInput("Enter the token (check your backend config.yml):")
28+ return a.Config.General.Token, nil
29+ }
30+ return a.Config.General.Token, nil
31+}
32+
33+func FieldCheck(a *cli.App) error {
34+ _, err := checkIfBackendURLisSet(a)
35+ if err != nil {
36+ return err
37+ }
38+ _, err = checkIfTokenIsSet(a)
39+ if err != nil {
40+ return err
41+ }
42+
43+ // save config
44+ err = a.Config.SaveConfig()
45+ if err != nil {
46+ return err
47+ }
48+
49+ return nil
50+}
M internal/cli/handler/startserver.go +3 -3
@@ -40,9 +40,9 @@ func StartServer(a *server.App, port string) error {
4040 e.HidePort = true
4141 e = httpserve.RegisterRoutes(e, a)
4242
43- log.Println("Starting server on port", port)
44- if err := e.Start(fmt.Sprintf(":%s", port)); err != nil {
45- log.Fatal("Server error:", err)
43+ log.Println("Starting server on port", a.Config.Http.Port)
44+ if err := e.Start(fmt.Sprintf(":%s", a.Config.Http.Port)); err != nil {
45+ log.Fatal(err)
4646 }
4747
4848 return nil
@@ -40,9 +40,9 @@ func StartServer(a *server.App, port string) error {
40 e.HidePort = true40 e.HidePort = true
41 e = httpserve.RegisterRoutes(e, a)41 e = httpserve.RegisterRoutes(e, a)
42 42
43- log.Println("Starting server on port", port)43+ log.Println("Starting server on port", a.Config.Http.Port)
44- if err := e.Start(fmt.Sprintf(":%s", port)); err != nil {44+ if err := e.Start(fmt.Sprintf(":%s", a.Config.Http.Port)); err != nil {
45- log.Fatal("Server error:", err)45+ log.Fatal(err)
46 }46 }
47 47
48 return nil48 return nil
M internal/common/initconfig.go +33 -3
@@ -12,6 +12,37 @@ import (
1212 "github.com/bnema/gordon/pkg/parser"
1313 )
1414
15+type Config struct {
16+ General GeneralConfig `yaml:"General"`
17+ Http HttpConfig `yaml:"Http"`
18+ Admin AdminConfig `yaml:"Admin"`
19+ ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
20+}
21+
22+type AdminConfig struct {
23+ Path string `yaml:"path"`
24+}
25+
26+type GeneralConfig struct {
27+ RunEnv string // come from env
28+ BuildVersion string // come from env
29+ StorageDir string `yaml:"storageDir"`
30+ Token string `yaml:"token"`
31+}
32+type HttpConfig struct {
33+ Port string `yaml:"port"`
34+ TopDomain string `yaml:"topDomain"`
35+ SubDomain string `yaml:"subDomain"`
36+ BackendURL string `yaml:"backendURL"`
37+}
38+
39+type ContainerEngineConfig struct {
40+ Sock string `yaml:"dockersock"`
41+ PodmanSock string `yaml:"podmansock"`
42+ Podman bool `yaml:"podman"`
43+ Network string `yaml:"network"`
44+}
45+
1546 var (
1647 sock = "/var/run/docker.sock"
1748 podmansock = "/run/user/1000/podman/podman.sock"
@@ -72,7 +103,7 @@ func (config *Config) LoadConfig() (*Config, error) {
72103 ContainerEngine: ContainerEngineConfig{
73104 Sock: sock,
74105 PodmanSock: podmansock,
75- Podman: readUserInput("Are you using podman ? (y/n)") == "y",
106+ Podman: ReadUserInput("Are you using podman ? (y/n)") == "y",
76107 },
77108 }
78109
@@ -95,12 +126,11 @@ func (config *Config) LoadConfig() (*Config, error) {
95126 // Load env elements
96127 config.General.BuildVersion = os.Getenv("BUILD_VERSION")
97128 config.General.RunEnv = os.Getenv("RUN_ENV")
98- config.General.BuildDir = os.Getenv("BUILD_DIR")
99129
100130 // if RUN_ENV is not set, assume "prod" and config dir is the current dir
101131 if config.General.RunEnv == "" {
102132 config.General.RunEnv = "prod"
103- config.General.BuildDir = "."
133+ configDir = "."
104134 }
105135
106136 return config, nil
@@ -12,6 +12,37 @@ import (
12 "github.com/bnema/gordon/pkg/parser"12 "github.com/bnema/gordon/pkg/parser"
13 )13 )
14 14
15+type Config struct {
16+ General GeneralConfig `yaml:"General"`
17+ Http HttpConfig `yaml:"Http"`
18+ Admin AdminConfig `yaml:"Admin"`
19+ ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
20+}
21+
22+type AdminConfig struct {
23+ Path string `yaml:"path"`
24+}
25+
26+type GeneralConfig struct {
27+ RunEnv string // come from env
28+ BuildVersion string // come from env
29+ StorageDir string `yaml:"storageDir"`
30+ Token string `yaml:"token"`
31+}
32+type HttpConfig struct {
33+ Port string `yaml:"port"`
34+ TopDomain string `yaml:"topDomain"`
35+ SubDomain string `yaml:"subDomain"`
36+ BackendURL string `yaml:"backendURL"`
37+}
38+
39+type ContainerEngineConfig struct {
40+ Sock string `yaml:"dockersock"`
41+ PodmanSock string `yaml:"podmansock"`
42+ Podman bool `yaml:"podman"`
43+ Network string `yaml:"network"`
44+}
45+
15 var (46 var (
16 sock = "/var/run/docker.sock"47 sock = "/var/run/docker.sock"
17 podmansock = "/run/user/1000/podman/podman.sock"48 podmansock = "/run/user/1000/podman/podman.sock"
@@ -72,7 +103,7 @@ func (config *Config) LoadConfig() (*Config, error) {
72 ContainerEngine: ContainerEngineConfig{103 ContainerEngine: ContainerEngineConfig{
73 Sock: sock,104 Sock: sock,
74 PodmanSock: podmansock,105 PodmanSock: podmansock,
75- Podman: readUserInput("Are you using podman ? (y/n)") == "y",106+ Podman: ReadUserInput("Are you using podman ? (y/n)") == "y",
76 },107 },
77 }108 }
78 109
@@ -95,12 +126,11 @@ func (config *Config) LoadConfig() (*Config, error) {
95 // Load env elements126 // Load env elements
96 config.General.BuildVersion = os.Getenv("BUILD_VERSION")127 config.General.BuildVersion = os.Getenv("BUILD_VERSION")
97 config.General.RunEnv = os.Getenv("RUN_ENV")128 config.General.RunEnv = os.Getenv("RUN_ENV")
98- config.General.BuildDir = os.Getenv("BUILD_DIR")
99 129
100 // if RUN_ENV is not set, assume "prod" and config dir is the current dir130 // if RUN_ENV is not set, assume "prod" and config dir is the current dir
101 if config.General.RunEnv == "" {131 if config.General.RunEnv == "" {
102 config.General.RunEnv = "prod"132 config.General.RunEnv = "prod"
103- config.General.BuildDir = "."133+ configDir = "."
104 }134 }
105 135
106 return config, nil136 return config, nil
M internal/common/inputs.go +2 -2
@@ -2,8 +2,8 @@ package common
22
33 import "fmt"
44
5-// readUserInput reads a string input from the user with a prompt.
6-func readUserInput(prompt string) string {
5+// ReadUserInput reads a string input from the user with a prompt.
6+func ReadUserInput(prompt string) string {
77 fmt.Println(prompt)
88 var input string
99 fmt.Scanln(&input)
@@ -2,8 +2,8 @@ package common
2 2
3 import "fmt"3 import "fmt"
4 4
5-// readUserInput reads a string input from the user with a prompt.5+// ReadUserInput reads a string input from the user with a prompt.
6-func readUserInput(prompt string) string {6+func ReadUserInput(prompt string) string {
7 fmt.Println(prompt)7 fmt.Println(prompt)
8 var input string8 var input string
9 fmt.Scanln(&input)9 fmt.Scanln(&input)
D internal/common/types.go +0 -36
@@ -1,36 +0,0 @@
1-package common
2-
3-type Config struct {
4- General GeneralConfig `yaml:"General"`
5- Http HttpConfig `yaml:"Http"`
6- Admin AdminConfig `yaml:"Admin"`
7- ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
8-}
9-
10-type AdminConfig struct {
11- Path string `yaml:"path"`
12-}
13-
14-type GeneralConfig struct {
15- RunEnv string // come from env
16- BuildDir string // come from env
17- BuildVersion string // come from env
18- StorageDir string `yaml:"storageDir"` // = buildir/storage
19- Token string `yaml:"token"`
20-}
21-type HttpConfig struct {
22- Port int `yaml:"port"`
23- TopDomain string `yaml:"topDomain"`
24- SubDomain string `yaml:"subDomain"`
25- BackendURL string `yaml:"backendURL"`
26-}
27-
28-type ContainerEngineConfig struct {
29- Sock string `yaml:"dockersock"`
30- PodmanSock string `yaml:"podmansock"`
31- Podman bool `yaml:"podman"`
32- Network string `yaml:"network"`
33-}
34-
35-// config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
36-// config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
@@ -1,36 +0,0 @@
1-package common
2-
3-type Config struct {
4- General GeneralConfig `yaml:"General"`
5- Http HttpConfig `yaml:"Http"`
6- Admin AdminConfig `yaml:"Admin"`
7- ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
8-}
9-
10-type AdminConfig struct {
11- Path string `yaml:"path"`
12-}
13-
14-type GeneralConfig struct {
15- RunEnv string // come from env
16- BuildDir string // come from env
17- BuildVersion string // come from env
18- StorageDir string `yaml:"storageDir"` // = buildir/storage
19- Token string `yaml:"token"`
20-}
21-type HttpConfig struct {
22- Port int `yaml:"port"`
23- TopDomain string `yaml:"topDomain"`
24- SubDomain string `yaml:"subDomain"`
25- BackendURL string `yaml:"backendURL"`
26-}
27-
28-type ContainerEngineConfig struct {
29- Sock string `yaml:"dockersock"`
30- PodmanSock string `yaml:"podmansock"`
31- Podman bool `yaml:"podman"`
32- Network string `yaml:"network"`
33-}
34-
35-// config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
36-// config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
M internal/server/appserver.go +1 -24
@@ -11,10 +11,6 @@ import (
1111 _ "github.com/joho/godotenv/autoload"
1212 )
1313
14-var (
15- OauthCallbackURL string
16-)
17-
1814 type App struct {
1915 TemplateFS fs.FS
2016 PublicFS fs.FS
@@ -42,7 +38,7 @@ func (a *App) GenerateOauthCallbackURL() string {
4238 config := a.Config
4339 if config.General.RunEnv == "dev" {
4440 scheme = "http"
45- port = fmt.Sprintf(":%d", config.Http.Port)
41+ port = ":" + config.Http.Port
4642 } else { // Assuming "prod"
4743 scheme = "https"
4844 port = ""
@@ -56,25 +52,6 @@ func (a *App) GenerateOauthCallbackURL() string {
5652 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
5753 }
5854
59-func (a *App) GenerateAPIURL() string {
60- var scheme, port string
61- config := a.Config
62- if config.General.RunEnv == "dev" {
63- scheme = "http"
64- port = fmt.Sprintf(":%d", config.Http.Port)
65- } else { // Assuming "prod"
66- scheme = "https"
67- port = ""
68- }
69-
70- domain := config.Http.TopDomain
71- if config.Http.SubDomain != "" {
72- domain = fmt.Sprintf("%s.%s", config.Http.SubDomain, config.Http.TopDomain)
73- }
74-
75- return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)
76-}
77-
7855 func (a *App) GetUptime() string {
7956 uptime := time.Since(a.StartTime)
8057 return uptime.String()
@@ -11,10 +11,6 @@ import (
11 _ "github.com/joho/godotenv/autoload"11 _ "github.com/joho/godotenv/autoload"
12 )12 )
13 13
14-var (
15- OauthCallbackURL string
16-)
17-
18 type App struct {14 type App struct {
19 TemplateFS fs.FS15 TemplateFS fs.FS
20 PublicFS fs.FS16 PublicFS fs.FS
@@ -42,7 +38,7 @@ func (a *App) GenerateOauthCallbackURL() string {
42 config := a.Config38 config := a.Config
43 if config.General.RunEnv == "dev" {39 if config.General.RunEnv == "dev" {
44 scheme = "http"40 scheme = "http"
45- port = fmt.Sprintf(":%d", config.Http.Port)41+ port = ":" + config.Http.Port
46 } else { // Assuming "prod"42 } else { // Assuming "prod"
47 scheme = "https"43 scheme = "https"
48 port = ""44 port = ""
@@ -56,25 +52,6 @@ func (a *App) GenerateOauthCallbackURL() string {
56 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)52 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
57 }53 }
58 54
59-func (a *App) GenerateAPIURL() string {
60- var scheme, port string
61- config := a.Config
62- if config.General.RunEnv == "dev" {
63- scheme = "http"
64- port = fmt.Sprintf(":%d", config.Http.Port)
65- } else { // Assuming "prod"
66- scheme = "https"
67- port = ""
68- }
69-
70- domain := config.Http.TopDomain
71- if config.Http.SubDomain != "" {
72- domain = fmt.Sprintf("%s.%s", config.Http.SubDomain, config.Http.TopDomain)
73- }
74-
75- return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)
76-}
77-
78 func (a *App) GetUptime() string {55 func (a *App) GetUptime() string {
79 uptime := time.Since(a.StartTime)56 uptime := time.Since(a.StartTime)
80 return uptime.String()57 return uptime.String()
M internal/server/newserver.go +5 -5
@@ -1,7 +1,6 @@
11 package server
22
33 import (
4- "fmt"
54 "io"
65 "log"
76 "time"
@@ -19,9 +18,6 @@ func NewServerApp() (*App, error) {
1918 log.Fatalf("Failed to load configuration: %v", err)
2019 }
2120
22- // Append build dir to storage dir
23- config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir)
24-
2521 // Open the strings.yml file containing the strings for the current language
2622 file, err := templating.TemplateFS.Open("locstrings.yml")
2723 if err != nil {
@@ -36,6 +32,9 @@ func NewServerApp() (*App, error) {
3632
3733 file.Close()
3834
35+ // Initialize DB
36+ DBDir := config.General.StorageDir + "/db"
37+
3938 // Initialize App
4039 a := &App{
4140 TemplateFS: templating.TemplateFS,
@@ -47,6 +46,7 @@ func NewServerApp() (*App, error) {
4746 StartTime: time.Now(),
4847 }
4948
50- OauthCallbackURL = a.GenerateOauthCallbackURL()
49+ a.GenerateOauthCallbackURL()
50+
5151 return a, nil
5252 }
@@ -1,7 +1,6 @@
1 package server1 package server
2 2
3 import (3 import (
4- "fmt"
5 "io"4 "io"
6 "log"5 "log"
7 "time"6 "time"
@@ -19,9 +18,6 @@ func NewServerApp() (*App, error) {
19 log.Fatalf("Failed to load configuration: %v", err)18 log.Fatalf("Failed to load configuration: %v", err)
20 }19 }
21 20
22- // Append build dir to storage dir
23- config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir)
24-
25 // Open the strings.yml file containing the strings for the current language21 // Open the strings.yml file containing the strings for the current language
26 file, err := templating.TemplateFS.Open("locstrings.yml")22 file, err := templating.TemplateFS.Open("locstrings.yml")
27 if err != nil {23 if err != nil {
@@ -36,6 +32,9 @@ func NewServerApp() (*App, error) {
36 32
37 file.Close()33 file.Close()
38 34
35+ // Initialize DB
36+ DBDir := config.General.StorageDir + "/db"
37+
39 // Initialize App38 // Initialize App
40 a := &App{39 a := &App{
41 TemplateFS: templating.TemplateFS,40 TemplateFS: templating.TemplateFS,
@@ -47,6 +46,7 @@ func NewServerApp() (*App, error) {
47 StartTime: time.Now(),46 StartTime: time.Now(),
48 }47 }
49 48
50- OauthCallbackURL = a.GenerateOauthCallbackURL()49+ a.GenerateOauthCallbackURL()
50+
51 return a, nil51 return a, nil
52 }52 }
M internal/server/sqliteinit.go +0 -1
@@ -12,7 +12,6 @@ import (
1212 )
1313
1414 const (
15- DBDir = "tmp/data"
1615 DBFilename = "sqlite.db"
1716 )
1817
@@ -12,7 +12,6 @@ import (
12 )12 )
13 13
14 const (14 const (
15- DBDir = "tmp/data"
16 DBFilename = "sqlite.db"15 DBFilename = "sqlite.db"
17 )16 )
18 17

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close