detached 511b814

Merge pull request #20 from bnema/improve/install-docs-and-user-logs

docs(install): add quick install script and fix logs user service fallback
commit
511b814996f12e38af5ea0787bda795900663c70
author
bnema · · 2026-01-16 05:13 UTC
committer
GitHub · · 2026-01-16 05:13 UTC
merge of
12cefde + 8f6a4dc · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v2.2.5
3 files · +63 -22
M docs/getting-started.md +3 -11
@@ -11,19 +11,11 @@ Deploy your first app with Gordon in under 5 minutes.
1111 ## 1. Install Gordon
1212
1313 ```bash
14-# Download the latest release (choose your architecture)
15-# For x86_64:
16-wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_amd64.tar.gz
17-tar -xzf gordon_linux_amd64.tar.gz
18-
19-# For ARM64 (Raspberry Pi 4, AWS Graviton, etc.):
20-# wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_arm64.tar.gz
21-# tar -xzf gordon_linux_arm64.tar.gz
22-
23-chmod +x gordon
24-sudo mv gordon /usr/local/bin/
14+curl -fsSL https://gordon.bnema.dev/install | bash
2515 ```
2616
17+This script automatically detects your OS and architecture, downloads the appropriate binary, and installs it to `/usr/local/bin`.
18+
2719 ## 2. Start Gordon
2820
2921 ```bash
@@ -11,19 +11,11 @@ Deploy your first app with Gordon in under 5 minutes.
11 ## 1. Install Gordon11 ## 1. Install Gordon
12 12
13 ```bash13 ```bash
14-# Download the latest release (choose your architecture)14+curl -fsSL https://gordon.bnema.dev/install | bash
15-# For x86_64:
16-wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_amd64.tar.gz
17-tar -xzf gordon_linux_amd64.tar.gz
18-
19-# For ARM64 (Raspberry Pi 4, AWS Graviton, etc.):
20-# wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_arm64.tar.gz
21-# tar -xzf gordon_linux_arm64.tar.gz
22-
23-chmod +x gordon
24-sudo mv gordon /usr/local/bin/
25 ```15 ```
26 16
17+This script automatically detects your OS and architecture, downloads the appropriate binary, and installs it to `/usr/local/bin`.
18+
27 ## 2. Start Gordon19 ## 2. Start Gordon
28 20
29 ```bash21 ```bash
M docs/installation.md +9 -1
@@ -12,7 +12,15 @@ Detailed installation guide for production environments.
1212
1313 ## Download Gordon
1414
15-### From GitHub Releases
15+### Quick Install (Recommended)
16+
17+```bash
18+curl -fsSL https://gordon.bnema.dev/install | bash
19+```
20+
21+This script automatically detects your OS (Linux/macOS) and architecture (amd64/arm64), downloads the appropriate binary from GitHub releases, and installs it to `/usr/local/bin`.
22+
23+### Manual Installation
1624
1725 **Linux (x86_64)**
1826 ```bash
@@ -12,7 +12,15 @@ Detailed installation guide for production environments.
12 12
13 ## Download Gordon13 ## Download Gordon
14 14
15-### From GitHub Releases15+### Quick Install (Recommended)
16+
17+```bash
18+curl -fsSL https://gordon.bnema.dev/install | bash
19+```
20+
21+This script automatically detects your OS (Linux/macOS) and architecture (amd64/arm64), downloads the appropriate binary from GitHub releases, and installs it to `/usr/local/bin`.
22+
23+### Manual Installation
16 24
17 **Linux (x86_64)**25 **Linux (x86_64)**
18 ```bash26 ```bash
M internal/app/logs.go +51 -10
@@ -3,6 +3,7 @@ package app
33
44 import (
55 "bufio"
6+ "bytes"
67 "fmt"
78 "os"
89 "os/exec"
@@ -192,8 +193,57 @@ func hasJournalctl() bool {
192193 }
193194
194195 // showJournalctlLog displays logs from journalctl.
196+// It first tries user service, then falls back to system service.
195197 func showJournalctlLog(follow bool, lines int) error {
198+ var userErr, systemErr error
199+
200+ // Try user service first (works without special permissions)
201+ hasLogs, err := hasJournalctlLogs(true)
202+ if err != nil {
203+ userErr = err
204+ } else if hasLogs {
205+ return runJournalctl(follow, lines, true)
206+ }
207+
208+ // Fall back to system service
209+ hasLogs, err = hasJournalctlLogs(false)
210+ if err != nil {
211+ systemErr = err
212+ } else if hasLogs {
213+ return runJournalctl(follow, lines, false)
214+ }
215+
216+ // If either check failed with an error, include that information
217+ if userErr != nil || systemErr != nil {
218+ return fmt.Errorf("gordon service not found in journalctl (user: %v, system: %v)", userErr, systemErr)
219+ }
220+
221+ return fmt.Errorf("gordon service not found in journalctl: enable file logging in config.toml or run gordon as a systemd service")
222+}
223+
224+// hasJournalctlLogs checks if there are any logs for the gordon service.
225+func hasJournalctlLogs(userService bool) (bool, error) {
226+ args := []string{"-u", "gordon", "-n", "1", "--no-pager", "-q"}
227+ if userService {
228+ args = append([]string{"--user"}, args...)
229+ }
230+
231+ // #nosec G204 -- args are constructed internally with controlled values
232+ cmd := exec.Command("journalctl", args...)
233+ output, err := cmd.Output()
234+ if err != nil {
235+ return false, err
236+ }
237+
238+ return len(bytes.TrimSpace(output)) > 0, nil
239+}
240+
241+// runJournalctl runs journalctl and displays the output.
242+func runJournalctl(follow bool, lines int, userService bool) error {
196243 args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"}
244+ if userService {
245+ args = append([]string{"--user"}, args...)
246+ }
197247 if follow {
198248 args = append(args, "-f")
199249 }
@@ -203,14 +253,5 @@ func showJournalctlLog(follow bool, lines int) error {
203253 cmd.Stdout = os.Stdout
204254 cmd.Stderr = os.Stderr
205255
206- err := cmd.Run()
207- if err != nil {
208- // Check if it's because the service doesn't exist
209- if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 {
210- return fmt.Errorf("gordon service not found in journalctl: enable file logging in config.toml or run gordon as a systemd service")
211- }
212- return fmt.Errorf("failed to run journalctl: %w", err)
213- }
214-
215- return nil
256+ return cmd.Run()
216257 }
@@ -3,6 +3,7 @@ package app
3 3
4 import (4 import (
5 "bufio"5 "bufio"
6+ "bytes"
6 "fmt"7 "fmt"
7 "os"8 "os"
8 "os/exec"9 "os/exec"
@@ -192,8 +193,57 @@ func hasJournalctl() bool {
192 }193 }
193 194
194 // showJournalctlLog displays logs from journalctl.195 // showJournalctlLog displays logs from journalctl.
196+// It first tries user service, then falls back to system service.
195 func showJournalctlLog(follow bool, lines int) error {197 func showJournalctlLog(follow bool, lines int) error {
198+ var userErr, systemErr error
199+
200+ // Try user service first (works without special permissions)
201+ hasLogs, err := hasJournalctlLogs(true)
202+ if err != nil {
203+ userErr = err
204+ } else if hasLogs {
205+ return runJournalctl(follow, lines, true)
206+ }
207+
208+ // Fall back to system service
209+ hasLogs, err = hasJournalctlLogs(false)
210+ if err != nil {
211+ systemErr = err
212+ } else if hasLogs {
213+ return runJournalctl(follow, lines, false)
214+ }
215+
216+ // If either check failed with an error, include that information
217+ if userErr != nil || systemErr != nil {
218+ return fmt.Errorf("gordon service not found in journalctl (user: %v, system: %v)", userErr, systemErr)
219+ }
220+
221+ return fmt.Errorf("gordon service not found in journalctl: enable file logging in config.toml or run gordon as a systemd service")
222+}
223+
224+// hasJournalctlLogs checks if there are any logs for the gordon service.
225+func hasJournalctlLogs(userService bool) (bool, error) {
226+ args := []string{"-u", "gordon", "-n", "1", "--no-pager", "-q"}
227+ if userService {
228+ args = append([]string{"--user"}, args...)
229+ }
230+
231+ // #nosec G204 -- args are constructed internally with controlled values
232+ cmd := exec.Command("journalctl", args...)
233+ output, err := cmd.Output()
234+ if err != nil {
235+ return false, err
236+ }
237+
238+ return len(bytes.TrimSpace(output)) > 0, nil
239+}
240+
241+// runJournalctl runs journalctl and displays the output.
242+func runJournalctl(follow bool, lines int, userService bool) error {
196 args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"}243 args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"}
244+ if userService {
245+ args = append([]string{"--user"}, args...)
246+ }
197 if follow {247 if follow {
198 args = append(args, "-f")248 args = append(args, "-f")
199 }249 }
@@ -203,14 +253,5 @@ func showJournalctlLog(follow bool, lines int) error {
203 cmd.Stdout = os.Stdout253 cmd.Stdout = os.Stdout
204 cmd.Stderr = os.Stderr254 cmd.Stderr = os.Stderr
205 255
206- err := cmd.Run()256+ return cmd.Run()
207- if err != nil {
208- // Check if it's because the service doesn't exist
209- if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 {
210- return fmt.Errorf("gordon service not found in journalctl: enable file logging in config.toml or run gordon as a systemd service")
211- }
212- return fmt.Errorf("failed to run journalctl: %w", err)
213- }
214-
215- return nil
216 }257 }

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