detached 6c52399

docs(install): add quick install script and fix logs user service fallback

- Simplify installation docs with curl-based quick install script
- Add user service detection in journalctl before falling back to system service
commit
6c523999c719f11d9d4dfc70a76e2daac67d2cda
author
brice · · 2026-01-16 05:07 UTC
parent
12cefde
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +50 -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 +38 -10
@@ -3,6 +3,7 @@ package app
33
44 import (
55 "bufio"
6+ "bytes"
67 "fmt"
78 "os"
89 "os/exec"
@@ -192,8 +193,44 @@ 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+ // Try user service first (works without special permissions)
199+ if hasLogs, _ := hasJournalctlLogs(true); hasLogs {
200+ return runJournalctl(follow, lines, true)
201+ }
202+
203+ // Fall back to system service
204+ if hasLogs, _ := hasJournalctlLogs(false); hasLogs {
205+ return runJournalctl(follow, lines, false)
206+ }
207+
208+ return fmt.Errorf("gordon service not found in journalctl: enable file logging in config.toml or run gordon as a systemd service")
209+}
210+
211+// hasJournalctlLogs checks if there are any logs for the gordon service.
212+func hasJournalctlLogs(userService bool) (bool, error) {
213+ args := []string{"-u", "gordon", "-n", "1", "--no-pager", "-q"}
214+ if userService {
215+ args = append([]string{"--user"}, args...)
216+ }
217+
218+ // #nosec G204 -- args are constructed internally with controlled values
219+ cmd := exec.Command("journalctl", args...)
220+ output, err := cmd.Output()
221+ if err != nil {
222+ return false, err
223+ }
224+
225+ return len(bytes.TrimSpace(output)) > 0, nil
226+}
227+
228+// runJournalctl runs journalctl and displays the output.
229+func runJournalctl(follow bool, lines int, userService bool) error {
196230 args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"}
231+ if userService {
232+ args = append([]string{"--user"}, args...)
233+ }
197234 if follow {
198235 args = append(args, "-f")
199236 }
@@ -203,14 +240,5 @@ func showJournalctlLog(follow bool, lines int) error {
203240 cmd.Stdout = os.Stdout
204241 cmd.Stderr = os.Stderr
205242
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
243+ return cmd.Run()
216244 }
@@ -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,44 @@ 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+ // Try user service first (works without special permissions)
199+ if hasLogs, _ := hasJournalctlLogs(true); hasLogs {
200+ return runJournalctl(follow, lines, true)
201+ }
202+
203+ // Fall back to system service
204+ if hasLogs, _ := hasJournalctlLogs(false); hasLogs {
205+ return runJournalctl(follow, lines, false)
206+ }
207+
208+ return fmt.Errorf("gordon service not found in journalctl: enable file logging in config.toml or run gordon as a systemd service")
209+}
210+
211+// hasJournalctlLogs checks if there are any logs for the gordon service.
212+func hasJournalctlLogs(userService bool) (bool, error) {
213+ args := []string{"-u", "gordon", "-n", "1", "--no-pager", "-q"}
214+ if userService {
215+ args = append([]string{"--user"}, args...)
216+ }
217+
218+ // #nosec G204 -- args are constructed internally with controlled values
219+ cmd := exec.Command("journalctl", args...)
220+ output, err := cmd.Output()
221+ if err != nil {
222+ return false, err
223+ }
224+
225+ return len(bytes.TrimSpace(output)) > 0, nil
226+}
227+
228+// runJournalctl runs journalctl and displays the output.
229+func runJournalctl(follow bool, lines int, userService bool) error {
196 args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"}230 args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"}
231+ if userService {
232+ args = append([]string{"--user"}, args...)
233+ }
197 if follow {234 if follow {
198 args = append(args, "-f")235 args = append(args, "-f")
199 }236 }
@@ -203,14 +240,5 @@ func showJournalctlLog(follow bool, lines int) error {
203 cmd.Stdout = os.Stdout240 cmd.Stdout = os.Stdout
204 cmd.Stderr = os.Stderr241 cmd.Stderr = os.Stderr
205 242
206- err := cmd.Run()243+ 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 }244 }

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