detached 40748ae

WIP

commit
40748ae5dcdb841a777b47c4a89694e8274057cd
author
bnema · · 2023-09-07 05:17 UTC
parent
db67c58
signature
unsigned
4 files · +137 -35
M internal/http/handlers/htmx-installer.go +11 -4
@@ -1,6 +1,8 @@
11 package handlers
22
33 import (
4+ "errors"
5+
46 "github.com/labstack/echo/v4"
57 "gogs.bnema.dev/gordon-echo/pkg/scripts"
68 "gogs.bnema.dev/gordon-echo/pkg/utils"
@@ -29,13 +31,18 @@ func TraefikInstallerHandler(c echo.Context) error {
2931 }
3032
3133 // Call to create the YAML files
32- err = scripts.CreateYAMLFiles(topDomain, adminEmail, logger)
34+ success, err := scripts.InstallTraefik(topDomain, adminEmail, logger)
3335 if err != nil {
34- logger.Error().Err(err).Msg("Failed to create YAML files")
36+ logger.Error().Err(err).Msg("Failed to install Traefik")
3537 return err
38+ } else if !success {
39+ logger.Error().Msg("Failed to install Traefik")
40+ return errors.New("failed to install Traefik")
41+ } else {
42+ logger.Info().Msg("Traefik installed successfully")
3643 }
37- return nil
38-
44+ // Return success message to the user wih htmx
45+ return c.HTML(200, "Traefik installed successfully")
3946 }
4047
4148 // use the handler at the route /admin/install/traefik
@@ -1,6 +1,8 @@
1 package handlers1 package handlers
2 2
3 import (3 import (
4+ "errors"
5+
4 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
5 "gogs.bnema.dev/gordon-echo/pkg/scripts"7 "gogs.bnema.dev/gordon-echo/pkg/scripts"
6 "gogs.bnema.dev/gordon-echo/pkg/utils"8 "gogs.bnema.dev/gordon-echo/pkg/utils"
@@ -29,13 +31,18 @@ func TraefikInstallerHandler(c echo.Context) error {
29 }31 }
30 32
31 // Call to create the YAML files33 // Call to create the YAML files
32- err = scripts.CreateYAMLFiles(topDomain, adminEmail, logger)34+ success, err := scripts.InstallTraefik(topDomain, adminEmail, logger)
33 if err != nil {35 if err != nil {
34- logger.Error().Err(err).Msg("Failed to create YAML files")36+ logger.Error().Err(err).Msg("Failed to install Traefik")
35 return err37 return err
38+ } else if !success {
39+ logger.Error().Msg("Failed to install Traefik")
40+ return errors.New("failed to install Traefik")
41+ } else {
42+ logger.Info().Msg("Traefik installed successfully")
36 }43 }
37- return nil44+ // Return success message to the user wih htmx
38-45+ return c.HTML(200, "Traefik installed successfully")
39 }46 }
40 47
41 // use the handler at the route /admin/install/traefik48 // use the handler at the route /admin/install/traefik
M pkg/docker/commands.go +43 -8
@@ -3,6 +3,7 @@ package docker
33 import (
44 "bufio"
55 "context"
6+ "errors"
67 "fmt"
78 "io"
89 "os"
@@ -174,26 +175,61 @@ func CreateNetwork(name string) error {
174175 return nil
175176 }
176177
178+// Check if a container exists
179+func ContainerExists(name string) (bool, error) {
180+ ctx := context.Background()
181+ cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
182+ if err != nil {
183+ return false, errors.New("failed to initialize Docker client")
184+ }
185+ defer cli.Close()
186+
187+ fmt.Println("Checking if container exists...")
188+ containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
189+ if err != nil {
190+ return false, errors.New("failed to list containers")
191+ }
192+
193+ for _, container := range containers {
194+ for _, containerName := range container.Names {
195+ if containerName == "/"+name {
196+ return true, nil
197+ }
198+ }
199+ }
200+
201+ return false, nil
202+}
203+
204+func CheckDockerRunning() (bool, error) {
205+ fmt.Println("Checking if Docker is running...")
206+ cmd := exec.Command("docker", "info")
207+ if err := cmd.Run(); err != nil {
208+ return true, errors.New("docker is not running")
209+ }
210+ return false, nil
211+}
212+
177213 // CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.
178-func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger) error {
214+func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger) (bool, error) {
179215 fmt.Println("Executing CreateContainerFromComposeFile()")
180216
181217 // Create and start containers using docker compose in the foreground
182- cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up")
218+ cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up", "-d")
183219
184220 // Get stdout and stderr pipes
185221 stdout, err := cmd.StdoutPipe()
186222 if err != nil {
187- return fmt.Errorf("failed to get stdout pipe: %v", err)
223+ return false, fmt.Errorf("failed to get stdout pipe: %v", err)
188224 }
189225 stderr, err := cmd.StderrPipe()
190226 if err != nil {
191- return fmt.Errorf("failed to get stderr pipe: %v", err)
227+ return false, fmt.Errorf("failed to get stderr pipe: %v", err)
192228 }
193229
194230 // Start the command
195231 if err := cmd.Start(); err != nil {
196- return fmt.Errorf("failed to start docker compose: %v", err)
232+ return false, fmt.Errorf("failed to start docker compose: %v", err)
197233 }
198234
199235 // Stream stdout to logger with INFO level
@@ -203,11 +239,10 @@ func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger
203239
204240 // Wait for the command to finish
205241 if err := cmd.Wait(); err != nil {
206- return fmt.Errorf("docker compose command finished with error: %v", err)
242+ return false, fmt.Errorf("docker compose command finished with error: %v", err)
207243 }
208244
209- fmt.Println("Containers started successfully using docker compose")
210- return nil
245+ return true, nil
211246 }
212247
213248 func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {
@@ -3,6 +3,7 @@ package docker
3 import (3 import (
4 "bufio"4 "bufio"
5 "context"5 "context"
6+ "errors"
6 "fmt"7 "fmt"
7 "io"8 "io"
8 "os"9 "os"
@@ -174,26 +175,61 @@ func CreateNetwork(name string) error {
174 return nil175 return nil
175 }176 }
176 177
178+// Check if a container exists
179+func ContainerExists(name string) (bool, error) {
180+ ctx := context.Background()
181+ cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
182+ if err != nil {
183+ return false, errors.New("failed to initialize Docker client")
184+ }
185+ defer cli.Close()
186+
187+ fmt.Println("Checking if container exists...")
188+ containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
189+ if err != nil {
190+ return false, errors.New("failed to list containers")
191+ }
192+
193+ for _, container := range containers {
194+ for _, containerName := range container.Names {
195+ if containerName == "/"+name {
196+ return true, nil
197+ }
198+ }
199+ }
200+
201+ return false, nil
202+}
203+
204+func CheckDockerRunning() (bool, error) {
205+ fmt.Println("Checking if Docker is running...")
206+ cmd := exec.Command("docker", "info")
207+ if err := cmd.Run(); err != nil {
208+ return true, errors.New("docker is not running")
209+ }
210+ return false, nil
211+}
212+
177 // CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.213 // CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.
178-func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger) error {214+func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger) (bool, error) {
179 fmt.Println("Executing CreateContainerFromComposeFile()")215 fmt.Println("Executing CreateContainerFromComposeFile()")
180 216
181 // Create and start containers using docker compose in the foreground217 // Create and start containers using docker compose in the foreground
182- cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up")218+ cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up", "-d")
183 219
184 // Get stdout and stderr pipes220 // Get stdout and stderr pipes
185 stdout, err := cmd.StdoutPipe()221 stdout, err := cmd.StdoutPipe()
186 if err != nil {222 if err != nil {
187- return fmt.Errorf("failed to get stdout pipe: %v", err)223+ return false, fmt.Errorf("failed to get stdout pipe: %v", err)
188 }224 }
189 stderr, err := cmd.StderrPipe()225 stderr, err := cmd.StderrPipe()
190 if err != nil {226 if err != nil {
191- return fmt.Errorf("failed to get stderr pipe: %v", err)227+ return false, fmt.Errorf("failed to get stderr pipe: %v", err)
192 }228 }
193 229
194 // Start the command230 // Start the command
195 if err := cmd.Start(); err != nil {231 if err := cmd.Start(); err != nil {
196- return fmt.Errorf("failed to start docker compose: %v", err)232+ return false, fmt.Errorf("failed to start docker compose: %v", err)
197 }233 }
198 234
199 // Stream stdout to logger with INFO level235 // Stream stdout to logger with INFO level
@@ -203,11 +239,10 @@ func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger
203 239
204 // Wait for the command to finish240 // Wait for the command to finish
205 if err := cmd.Wait(); err != nil {241 if err := cmd.Wait(); err != nil {
206- return fmt.Errorf("docker compose command finished with error: %v", err)242+ return false, fmt.Errorf("docker compose command finished with error: %v", err)
207 }243 }
208 244
209- fmt.Println("Containers started successfully using docker compose")245+ return true, nil
210- return nil
211 }246 }
212 247
213 func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {248 func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {
M pkg/scripts/install-traefik.go +82 -22
@@ -12,32 +12,56 @@ import (
1212 "gogs.bnema.dev/gordon-echo/pkg/utils"
1313 )
1414
15+var path string
16+
17+func init() {
18+ path = app.GetBuildDir() + "/"
19+}
20+
21+func InstallTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
22+ // Create the YAML files
23+ if success, err := createYAMLFiles(topDomain, adminEmail, logger); err != nil {
24+ return false, err
25+ } else if !success {
26+ return false, errors.New("failed to create YAML files")
27+ }
28+
29+ // Deploy the containers
30+ if success, err := deployTraefik(topDomain, adminEmail, logger); err != nil {
31+ return false, err
32+ } else if !success {
33+ return false, errors.New("failed to deploy Traefik")
34+ }
35+
36+ return true, nil
37+}
38+
1539 func ensureDirExists(dir string) error {
1640 if _, err := os.Stat(dir); os.IsNotExist(err) {
1741 return os.Mkdir(dir, 0755)
1842 }
1943 return nil
2044 }
21-func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
22- path := app.GetBuildDir() + "/"
45+
46+func createYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
2347 modelFS := root.ModelFS
2448 if modelFS == nil {
25- return errors.New("modelFS is nil")
49+ return false, errors.New("modelFS is nil")
2650 }
2751
2852 // Check if directory traefik exists, if not create it
2953 if err := ensureDirExists(path + "traefik"); err != nil {
30- return err
54+ return false, err
3155 }
3256 // Check if directory traefik/volume exists, if not create it
3357 if err := ensureDirExists(path + "traefik/volume"); err != nil {
34- return err
58+ return false, err
3559 }
3660
3761 // Setup the TXT renderer for traefik-compose.goyml
3862 rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", modelFS, logger)
3963 if err != nil {
40- return err
64+ return false, err
4165 }
4266
4367 // Render the traefik-compose.goyml with payload data
@@ -45,18 +69,18 @@ func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
4569 "topdomain": topDomain,
4670 })
4771 if err != nil {
48- return err
72+ return false, err
4973 }
5074 // Save the rendered content to a file at the env path
5175 err = os.WriteFile(path+"traefik/docker-compose.yml", []byte(composeContent), 0644)
5276 if err != nil {
53- return err
77+ return false, err
5478 }
5579
5680 // Setup the TXT renderer for traefik.goyml
5781 rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", modelFS, logger)
5882 if err != nil {
59- return err
83+ return false, err
6084 }
6185
6286 // Render the traefik.goyml with payload
@@ -64,49 +88,85 @@ func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
6488 "admin.email": adminEmail,
6589 })
6690 if err != nil {
67- return err
91+ return false, err
6892 }
6993
7094 // Save the rendered content to a file at the env path
7195 err = os.WriteFile(path+"traefik/traefik.yml", []byte(traefikContent), 0644)
7296 if err != nil {
73- return err
97+ return false, err
7498 }
7599
100+ return true, nil
101+
102+}
103+
104+func deployTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
76105 // Now that we have the YAML files, we can launch the containers
77106 // Create acme.json with permissions 600
78107 if f, err := os.Create(path + "traefik/volume/acme.json"); err != nil {
79- return err
108+ return false, err
109+ } else if err = f.Chmod(0600); err != nil {
110+ return false, err
111+ } else {
112+ f.Close() // Schedule closing of the file when function exits
113+ }
114+
115+ // traefik.log
116+ if f, err := os.Create(path + "traefik/traefik.log"); err != nil {
117+ return false, err
80118 } else if err = f.Chmod(0600); err != nil {
81- return err
119+ return false, err
82120 } else {
83121 f.Close() // Schedule closing of the file when function exits
84122 }
85123
124+ // check if docker is running
125+ if s, err := docker.CheckDockerRunning(); err != nil {
126+ return false, err
127+ } else if s {
128+ fmt.Println("Docker is running")
129+ } else {
130+ fmt.Println("Docker is not running")
131+ }
132+
133+ // check if a container named traefik already exists
134+ if exists, err := docker.ContainerExists("traefik"); err != nil {
135+ return false, err
136+ } else if exists {
137+ return false, errors.New("container traefik already exists")
138+ } else {
139+ fmt.Println("Container Traefik not found, creating it")
140+ }
141+
142+ // check if there is already a container named traefik
143+
86144 // Create the network "exposed" if it doesn't exist
87145 // Ensure that the network "exposed" exists if not create it
88146 exists, err := docker.NetworkExists("exposed")
89147 if err != nil {
90- return err
148+ return false, err
91149 }
92150 if exists {
93151 fmt.Println("Network Exposed found")
94152 } else {
95153 fmt.Println("Network Exposed not found, creating it")
96154 if err := docker.CreateNetwork("exposed"); err != nil {
97- return err
155+ return false, err
98156 }
99157 }
100158
101159 fmt.Println("Network created successfully")
102160
103- // Create the containers with CreateContainerFromComposeFile
104- if err := docker.CreateContainerFromComposeFile(path+"traefik/docker-compose.yml", logger); err != nil {
105- return err
161+ // Create the containers with CreateContainerFromComposeFile (bool, error)
162+ succes, err := docker.CreateContainerFromComposeFile(path+"traefik/docker-compose.yml", logger)
163+ if err != nil {
164+ return false, err
165+ }
166+ if succes {
167+ fmt.Println("Containers created successfully")
168+ return true, nil
106169 }
107170
108- fmt.Println("Containers created successfully")
109-
110- return nil
111-
171+ return false, errors.New("failed to create containers")
112172 }
@@ -12,32 +12,56 @@ import (
12 "gogs.bnema.dev/gordon-echo/pkg/utils"12 "gogs.bnema.dev/gordon-echo/pkg/utils"
13 )13 )
14 14
15+var path string
16+
17+func init() {
18+ path = app.GetBuildDir() + "/"
19+}
20+
21+func InstallTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
22+ // Create the YAML files
23+ if success, err := createYAMLFiles(topDomain, adminEmail, logger); err != nil {
24+ return false, err
25+ } else if !success {
26+ return false, errors.New("failed to create YAML files")
27+ }
28+
29+ // Deploy the containers
30+ if success, err := deployTraefik(topDomain, adminEmail, logger); err != nil {
31+ return false, err
32+ } else if !success {
33+ return false, errors.New("failed to deploy Traefik")
34+ }
35+
36+ return true, nil
37+}
38+
15 func ensureDirExists(dir string) error {39 func ensureDirExists(dir string) error {
16 if _, err := os.Stat(dir); os.IsNotExist(err) {40 if _, err := os.Stat(dir); os.IsNotExist(err) {
17 return os.Mkdir(dir, 0755)41 return os.Mkdir(dir, 0755)
18 }42 }
19 return nil43 return nil
20 }44 }
21-func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {45+
22- path := app.GetBuildDir() + "/"46+func createYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
23 modelFS := root.ModelFS47 modelFS := root.ModelFS
24 if modelFS == nil {48 if modelFS == nil {
25- return errors.New("modelFS is nil")49+ return false, errors.New("modelFS is nil")
26 }50 }
27 51
28 // Check if directory traefik exists, if not create it52 // Check if directory traefik exists, if not create it
29 if err := ensureDirExists(path + "traefik"); err != nil {53 if err := ensureDirExists(path + "traefik"); err != nil {
30- return err54+ return false, err
31 }55 }
32 // Check if directory traefik/volume exists, if not create it56 // Check if directory traefik/volume exists, if not create it
33 if err := ensureDirExists(path + "traefik/volume"); err != nil {57 if err := ensureDirExists(path + "traefik/volume"); err != nil {
34- return err58+ return false, err
35 }59 }
36 60
37 // Setup the TXT renderer for traefik-compose.goyml61 // Setup the TXT renderer for traefik-compose.goyml
38 rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", modelFS, logger)62 rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", modelFS, logger)
39 if err != nil {63 if err != nil {
40- return err64+ return false, err
41 }65 }
42 66
43 // Render the traefik-compose.goyml with payload data67 // Render the traefik-compose.goyml with payload data
@@ -45,18 +69,18 @@ func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
45 "topdomain": topDomain,69 "topdomain": topDomain,
46 })70 })
47 if err != nil {71 if err != nil {
48- return err72+ return false, err
49 }73 }
50 // Save the rendered content to a file at the env path74 // Save the rendered content to a file at the env path
51 err = os.WriteFile(path+"traefik/docker-compose.yml", []byte(composeContent), 0644)75 err = os.WriteFile(path+"traefik/docker-compose.yml", []byte(composeContent), 0644)
52 if err != nil {76 if err != nil {
53- return err77+ return false, err
54 }78 }
55 79
56 // Setup the TXT renderer for traefik.goyml80 // Setup the TXT renderer for traefik.goyml
57 rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", modelFS, logger)81 rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", modelFS, logger)
58 if err != nil {82 if err != nil {
59- return err83+ return false, err
60 }84 }
61 85
62 // Render the traefik.goyml with payload86 // Render the traefik.goyml with payload
@@ -64,49 +88,85 @@ func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
64 "admin.email": adminEmail,88 "admin.email": adminEmail,
65 })89 })
66 if err != nil {90 if err != nil {
67- return err91+ return false, err
68 }92 }
69 93
70 // Save the rendered content to a file at the env path94 // Save the rendered content to a file at the env path
71 err = os.WriteFile(path+"traefik/traefik.yml", []byte(traefikContent), 0644)95 err = os.WriteFile(path+"traefik/traefik.yml", []byte(traefikContent), 0644)
72 if err != nil {96 if err != nil {
73- return err97+ return false, err
74 }98 }
75 99
100+ return true, nil
101+
102+}
103+
104+func deployTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
76 // Now that we have the YAML files, we can launch the containers105 // Now that we have the YAML files, we can launch the containers
77 // Create acme.json with permissions 600106 // Create acme.json with permissions 600
78 if f, err := os.Create(path + "traefik/volume/acme.json"); err != nil {107 if f, err := os.Create(path + "traefik/volume/acme.json"); err != nil {
79- return err108+ return false, err
109+ } else if err = f.Chmod(0600); err != nil {
110+ return false, err
111+ } else {
112+ f.Close() // Schedule closing of the file when function exits
113+ }
114+
115+ // traefik.log
116+ if f, err := os.Create(path + "traefik/traefik.log"); err != nil {
117+ return false, err
80 } else if err = f.Chmod(0600); err != nil {118 } else if err = f.Chmod(0600); err != nil {
81- return err119+ return false, err
82 } else {120 } else {
83 f.Close() // Schedule closing of the file when function exits121 f.Close() // Schedule closing of the file when function exits
84 }122 }
85 123
124+ // check if docker is running
125+ if s, err := docker.CheckDockerRunning(); err != nil {
126+ return false, err
127+ } else if s {
128+ fmt.Println("Docker is running")
129+ } else {
130+ fmt.Println("Docker is not running")
131+ }
132+
133+ // check if a container named traefik already exists
134+ if exists, err := docker.ContainerExists("traefik"); err != nil {
135+ return false, err
136+ } else if exists {
137+ return false, errors.New("container traefik already exists")
138+ } else {
139+ fmt.Println("Container Traefik not found, creating it")
140+ }
141+
142+ // check if there is already a container named traefik
143+
86 // Create the network "exposed" if it doesn't exist144 // Create the network "exposed" if it doesn't exist
87 // Ensure that the network "exposed" exists if not create it145 // Ensure that the network "exposed" exists if not create it
88 exists, err := docker.NetworkExists("exposed")146 exists, err := docker.NetworkExists("exposed")
89 if err != nil {147 if err != nil {
90- return err148+ return false, err
91 }149 }
92 if exists {150 if exists {
93 fmt.Println("Network Exposed found")151 fmt.Println("Network Exposed found")
94 } else {152 } else {
95 fmt.Println("Network Exposed not found, creating it")153 fmt.Println("Network Exposed not found, creating it")
96 if err := docker.CreateNetwork("exposed"); err != nil {154 if err := docker.CreateNetwork("exposed"); err != nil {
97- return err155+ return false, err
98 }156 }
99 }157 }
100 158
101 fmt.Println("Network created successfully")159 fmt.Println("Network created successfully")
102 160
103- // Create the containers with CreateContainerFromComposeFile161+ // Create the containers with CreateContainerFromComposeFile (bool, error)
104- if err := docker.CreateContainerFromComposeFile(path+"traefik/docker-compose.yml", logger); err != nil {162+ succes, err := docker.CreateContainerFromComposeFile(path+"traefik/docker-compose.yml", logger)
105- return err163+ if err != nil {
164+ return false, err
165+ }
166+ if succes {
167+ fmt.Println("Containers created successfully")
168+ return true, nil
106 }169 }
107 170
108- fmt.Println("Containers created successfully")171+ return false, errors.New("failed to create containers")
109-
110- return nil
111-
112 }172 }
M pkg/templating/models/traefik-compose.goyml +1 -1
@@ -11,7 +11,7 @@ services:
1111 volumes:
1212 - /var/run/docker.sock:/var/run/docker.sock:ro
1313 - ./traefik.yml:/etc/traefik/traefik.yml
14- - ./traefik/volume/acme.json:/acme.json
14+ - ./volume/acme.json:/acme.json
1515 - ./traefik.log:/var/log/traefik/traefik.log
1616 labels:
1717 - "traefik.enable=true"
@@ -11,7 +11,7 @@ services:
11 volumes:11 volumes:
12 - /var/run/docker.sock:/var/run/docker.sock:ro12 - /var/run/docker.sock:/var/run/docker.sock:ro
13 - ./traefik.yml:/etc/traefik/traefik.yml13 - ./traefik.yml:/etc/traefik/traefik.yml
14- - ./traefik/volume/acme.json:/acme.json14+ - ./volume/acme.json:/acme.json
15 - ./traefik.log:/var/log/traefik/traefik.log15 - ./traefik.log:/var/log/traefik/traefik.log
16 labels:16 labels:
17 - "traefik.enable=true"17 - "traefik.enable=true"

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 8

Tags 106

↑↓ moveenter openesc close