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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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. | |||
| 11 | 11 | ## 1. Install Gordon | |
| 12 | 12 | ||
| 13 | 13 | ```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 | |
| 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 | 19 | ## 2. Start Gordon | |
| 28 | 20 | ||
| 29 | 21 | ```bash | |
| @@ -11,19 +11,11 @@ Deploy your first app with Gordon in under 5 minutes. | |||||
| 11 | ## 1. Install Gordon | 11 | ## 1. Install Gordon | ||
| 12 | 12 | ||||
| 13 | ```bash | 13 | ```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 Gordon | 19 | ## 2. Start Gordon | ||
| 28 | 20 | ||||
| 29 | ```bash | 21 | ```bash | ||
M docs/installation.md +9 -1
| @@ -12,7 +12,15 @@ Detailed installation guide for production environments. | |||
| 12 | 12 | ||
| 13 | 13 | ## Download Gordon | |
| 14 | 14 | ||
| 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 | |
| 16 | 24 | ||
| 17 | 25 | **Linux (x86_64)** | |
| 18 | 26 | ```bash | |
| @@ -12,7 +12,15 @@ Detailed installation guide for production environments. | |||||
| 12 | 12 | ||||
| 13 | ## Download Gordon | 13 | ## Download Gordon | ||
| 14 | 14 | ||||
| 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 | |||
| 16 | 24 | ||||
| 17 | **Linux (x86_64)** | 25 | **Linux (x86_64)** | ||
| 18 | ```bash | 26 | ```bash | ||
M internal/app/logs.go +38 -10
| @@ -3,6 +3,7 @@ package app | |||
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | 5 | "bufio" | |
| 6 | + | "bytes" | |
| 6 | 7 | "fmt" | |
| 7 | 8 | "os" | |
| 8 | 9 | "os/exec" | |
| @@ -192,8 +193,44 @@ func hasJournalctl() bool { | |||
| 192 | 193 | } | |
| 193 | 194 | ||
| 194 | 195 | // showJournalctlLog displays logs from journalctl. | |
| 196 | + | // It first tries user service, then falls back to system service. | |
| 195 | 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 | 230 | args := []string{"-u", "gordon", "-n", fmt.Sprintf("%d", lines), "--no-pager"} | |
| 231 | + | if userService { | |
| 232 | + | args = append([]string{"--user"}, args...) | |
| 233 | + | } | |
| 197 | 234 | if follow { | |
| 198 | 235 | args = append(args, "-f") | |
| 199 | 236 | } | |
| @@ -203,14 +240,5 @@ func showJournalctlLog(follow bool, lines int) error { | |||
| 203 | 240 | cmd.Stdout = os.Stdout | |
| 204 | 241 | cmd.Stderr = os.Stderr | |
| 205 | 242 | ||
| 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() | |
| 216 | 244 | } | |
| @@ -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.Stdout | 240 | cmd.Stdout = os.Stdout | ||
| 204 | cmd.Stderr = os.Stderr | 241 | 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 | } | ||