fix: resolve 13 release-consistency findings

Documentation:
- Fix CI badge href to /actions (was 404 on Gitea)
- Add UPTOP_METRICS_PUBLIC + UPTOP_MAINT_RETENTION to README env table
- Link maintenance retention to env var name in data retention section
- Note metrics auth requirement in features list
- Fix clustering.md: fail-closed wording, mark AGG_STRATEGY/NODE_REGION optional
- Fix .env.example: wording (no .env loader), add TRUSTED_PROXIES + MAINT_RETENTION
- Add CLI help/usage with subcommand listing, accept serve/help/-h/-version

Docker/deploy:
- Add EXPOSE 8080 to Dockerfile
- Remove dead LIPGLOSS_RENDERER_HAS_DARK_BACKGROUND env
- Exempt /api/health from cluster auth (fixes Docker HEALTHCHECK 401)
- Add sysctls for unprivileged ping to all compose files

Cosmetic:
- Fix bug_report.yaml: SemVer placeholder, remove nonexistent serve subcommand
This commit is contained in:
2026-06-19 20:09:03 -04:00
parent 47d3b0e68f
commit b32145fb58
11 changed files with 45 additions and 20 deletions
+3 -1
View File
@@ -1,5 +1,5 @@
# ─── uptop configuration ───────────────────────────────────
# Copy to .env and edit. Only uncomment what you need.
# Export in your environment or pass via docker run --env-file.\n# Only uncomment what you need.
# ─── Core ──────────────────────────────────────────────────
UPTOP_PORT=23234 # SSH server port
@@ -40,3 +40,5 @@ UPTOP_DB_DSN=/data/uptop.db # File path (SQLite) or connection string (Postgre
# UPTOP_ALLOW_PRIVATE_TARGETS=false # Allow monitoring RFC1918/loopback addresses
# UPTOP_METRICS_PUBLIC=false # Expose /metrics without auth
# UPTOP_CORS_ORIGIN= # Access-Control-Allow-Origin for /status/json
# UPTOP_TRUSTED_PROXIES= # Comma-separated CIDRs/IPs for X-Forwarded-For trust
# UPTOP_MAINT_RETENTION=168h # How long ended maintenance windows are kept
+3 -3
View File
@@ -16,7 +16,7 @@ body:
label: What happened?
description: Include what you expected to happen instead.
placeholder: |
When I run `uptop serve`, the TUI crashes after 10 seconds.
When I run `uptop`, the TUI crashes after 10 seconds.
I expected it to keep running and display monitor status.
validations:
required: true
@@ -25,7 +25,7 @@ body:
attributes:
label: Steps to reproduce
placeholder: |
1. Run `uptop serve`
1. Run `uptop`
2. Wait ~10 seconds
3. TUI crashes with panic
validations:
@@ -37,7 +37,7 @@ body:
description: Output of `uptop version`, OS, terminal. Paste any errors below.
render: shell
placeholder: |
uptop version 2026.06.1
uptop 0.1.0 (abc1234, 2026-06-17)
OS: Debian 13
Terminal: Ghostty
+1 -2
View File
@@ -23,14 +23,13 @@ RUN mkdir -p /data/.ssh && chown -R uptop:uptop /data
COPY --from=builder /app/uptop .
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/
ENV LIPGLOSS_RENDERER_HAS_DARK_BACKGROUND=true
ENV UPTOP_DB_TYPE=sqlite
ENV UPTOP_DB_DSN=/data/uptop.db
ENV UPTOP_KEYS=/data/authorized_keys
ENV UPTOP_SSH_HOST_KEY=/data/.ssh/id_ed25519
ENV UPTOP_PORT=23234
EXPOSE 23234
EXPOSE 8080 23234
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:8080/api/health || exit 1
USER uptop
+5 -3
View File
@@ -4,7 +4,7 @@
<p>No browser. No client install. Just <code>ssh -p 23234 your-server</code>.</p>
<p>
<a href="https://gitea.lerkolabs.com/lerkolabs/uptop/actions/workflows/ci.yml"><img src="https://gitea.lerkolabs.com/lerkolabs/uptop/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://gitea.lerkolabs.com/lerkolabs/uptop/actions"><img src="https://gitea.lerkolabs.com/lerkolabs/uptop/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License">
<img src="https://img.shields.io/badge/go-1.26-00ADD8?logo=go&logoColor=white" alt="Go 1.26">
<img src="https://img.shields.io/docker/pulls/lerkolabs/uptop" alt="Docker Pulls">
@@ -27,7 +27,7 @@ Canonical repo: [gitea.lerkolabs.com/lerkolabs/uptop](https://gitea.lerkolabs.co
- **10 alert providers** — Discord, Slack, Email, Ntfy, Webhook, Telegram, PagerDuty, Pushover, Gotify, Opsgenie
- **Config as code** — define monitors in YAML, apply declaratively, version control your setup
- **HA clustering** — leader/follower with automatic failover
- **Prometheus metrics** — `/metrics` endpoint, wire it straight to Grafana
- **Prometheus metrics** — `/metrics` endpoint (`UPTOP_METRICS_PUBLIC=true` to expose without auth)
- **Public status page** — HTML + JSON, toggle with an env var
- **SQLite or Postgres** — SQLite for single-node, Postgres for production
- **Uptime Kuma import** — migrate from Kuma with one command
@@ -146,6 +146,8 @@ Full reference in [docs/config-as-code.md](docs/config-as-code.md).
| `UPTOP_INSECURE_SKIP_VERIFY` | `false` | Skip TLS verification for checks |
| `UPTOP_ALLOW_PRIVATE_TARGETS` | `false` | Allow monitoring RFC1918/loopback addresses |
| `UPTOP_ADMIN_KEY` | | SSH public key seeded as first admin on startup |
| `UPTOP_METRICS_PUBLIC` | `false` | Expose `/metrics` without auth |
| `UPTOP_MAINT_RETENTION` | `168h` | How long ended maintenance windows are kept |
| `UPTOP_TRUSTED_PROXIES` | | Comma-separated CIDRs/IPs whose `X-Forwarded-For` is trusted ([details](#running-behind-a-reverse-proxy)) |
See [`.env.example`](.env.example) for all options including TLS, probes, and advanced settings.
@@ -179,7 +181,7 @@ uptop prunes its own history in the background — no external cleanup jobs need
| Check history | newest 1,000 checks per monitor |
| State changes (UP/DOWN transitions) | newest 5,000 per monitor |
| Logs | newest 200 entries |
| Maintenance windows | 7 days after they end (configurable) |
| Maintenance windows | 7 days after they end (`UPTOP_MAINT_RETENTION`) |
Sparklines, uptime percentages, and SLA reports are computed from these windows, so very long-horizon stats aren't retained. Export to Prometheus via `/metrics` if you need unlimited history.
+21 -1
View File
@@ -77,17 +77,37 @@ func main() {
case "export":
runExport(os.Args[2:])
return
case "version", "--version", "-v":
case "version", "--version", "-v", "-version":
printVersion()
return
case "migrate-secrets":
runMigrateSecrets(os.Args[2:])
return
case "help", "--help", "-h":
printUsage()
return
case "serve":
runServe(os.Args[2:])
return
}
}
runServe(os.Args[1:])
}
func printUsage() {
fmt.Fprintf(os.Stderr, `Usage: uptop <command> [flags]
Commands:
serve Start the server (default if no command given)
apply Apply monitors from a YAML file
export Export monitors to YAML
migrate-secrets Re-encrypt alert credentials with current key
version Print version and exit
Run 'uptop serve --help' for server flags.
`)
}
func printVersion() {
out := "uptop " + version
var meta []string
+4
View File
@@ -5,6 +5,8 @@ services:
leader:
image: lerkolabs/uptop:latest
container_name: uptop-leader
sysctls:
- net.ipv4.ping_group_range=0 2147483647
ports:
- "23234:23234" # SSH
- "8080:8080" # HTTP
@@ -40,6 +42,8 @@ services:
follower:
image: lerkolabs/uptop:latest
container_name: uptop-follower
sysctls:
- net.ipv4.ping_group_range=0 2147483647
ports:
- "23233:23234" # SSH (Mapped to different host port)
- "8081:8080" # HTTP (Mapped to different host port)
+2
View File
@@ -1,6 +1,8 @@
services:
leader:
image: lerkolabs/uptop:latest
sysctls:
- net.ipv4.ping_group_range=0 2147483647
environment:
- UPTOP_CLUSTER_MODE=leader
- UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use
+2
View File
@@ -8,6 +8,8 @@ services:
- ALL
security_opt:
- no-new-privileges:true
sysctls:
- net.ipv4.ping_group_range=0 2147483647
tmpfs:
- /tmp
ports:
+2 -4
View File
@@ -47,13 +47,11 @@ Probes are lightweight, stateless nodes that run checks from different locations
| Node | Variable | Value |
|------|----------|-------|
| Both | `UPTOP_CLUSTER_SECRET` | Same shared secret |
| Leader | `UPTOP_AGG_STRATEGY` | `any-down`, `majority-down`, or `all-down` |
| Probe | `UPTOP_CLUSTER_MODE` | `probe` |
| Probe | `UPTOP_PEER_URL` | Leader's HTTP URL |
| Probe | `UPTOP_NODE_ID` | Unique identifier (e.g. `probe-us-east`) |
| Probe | `UPTOP_NODE_REGION` | Region tag matching monitor assignments |
Optional: `UPTOP_NODE_NAME` for a human-readable label in the TUI.
Optional: `UPTOP_AGG_STRATEGY` (default `any-down`), `UPTOP_NODE_REGION` (omit to match all monitors), `UPTOP_NODE_NAME` (human-readable label in the TUI).
See [`deploy/docker-compose.probe.yml`](../deploy/docker-compose.probe.yml) for a multi-region example.
@@ -80,6 +78,6 @@ Set via `UPTOP_AGG_STRATEGY` on the leader.
## Security
- Set `UPTOP_CLUSTER_SECRET` on all nodes. Without it, cluster API endpoints are unauthenticated.
- Set `UPTOP_CLUSTER_SECRET` on all nodes. Without it, cluster API endpoints reject all requests (fail closed); only `/api/health` stays open.
- Secrets are sent in HTTP headers (`X-Uptop-Secret`). Use TLS or a reverse proxy for production.
- uptop warns on startup if the cluster secret is missing or if cluster mode is active without TLS.
-4
View File
@@ -159,10 +159,6 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if s.cfg.ClusterKey != "" && !checkSecret(r.Header.Get("X-Uptop-Secret"), s.cfg.ClusterKey) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}
+2 -2
View File
@@ -219,8 +219,8 @@ func TestHealth_WrongSecret(t *testing.T) {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 401 {
t.Errorf("expected 401, got %d", resp.StatusCode)
if resp.StatusCode != 200 {
t.Errorf("health is unauthenticated, expected 200, got %d", resp.StatusCode)
}
}