Commit Graph

166 Commits

Author SHA1 Message Date
lerko d316c5cf1b docs: fix PEER_URL description to include probe nodes
CI / test (pull_request) Successful in 2m25s
CI / lint (pull_request) Successful in 46s
CI / vulncheck (pull_request) Failing after 40s
2026-06-02 17:30:37 -04:00
lerko 99c0ad8072 chore: add .env and .github/ to .dockerignore
Prevent secrets from leaking into Docker build context.
GitHub workflows don't belong in the image.
2026-06-02 17:30:37 -04:00
lerko 048b245d25 docs: add env reference, clustering guide, and README improvements
- .env.example: complete env var reference (21 vars, grouped, commented)
- docs/clustering.md: leader/follower/probe setup, aggregation, security
- README: encryption section, clustering summary, upgrading note,
  ALLOW_PRIVATE_TARGETS + ENCRYPTION_KEY in env table, link to .env.example
- .gitignore: add .env to prevent credential leaks
2026-06-02 17:30:37 -04:00
lerko 21cbaa9ff9 docs: point release downloads to GitHub mirror
GitHub is the public storefront. Binary releases link to GitHub
where the relay workflow publishes them. Clarify Linux amd64 only
for binary downloads.
2026-06-02 17:30:37 -04:00
lerko 056cf9c3f7 docs: add missing changelog entries for 2026.05.6 and 2026.06.2
Both tags were infrastructure-only (CI/CD pipeline work).
Marked as (infrastructure) to distinguish from user-facing releases.
2026-06-02 17:30:37 -04:00
lerko 737ada2f5e Merge pull request 'feat(ci): add GitHub release relay workflow' (#49) from feat/github-release-relay into main
CI / test (push) Successful in 2m20s
CI / lint (push) Successful in 45s
CI / vulncheck (push) Successful in 41s
Release Binaries / release (push) Successful in 2m6s
Release Docker / docker (push) Successful in 21m5s
Reviewed-on: #49
2026-06-02 18:33:21 +00:00
lerko f9e7a4d473 feat(ci): add GitHub release relay workflow
CI / test (pull_request) Successful in 2m21s
CI / lint (pull_request) Successful in 46s
CI / vulncheck (pull_request) Successful in 41s
Mirror pushes tags to GitHub but not releases. This workflow
triggers on tag push, polls Gitea API for the release and
artifacts, then creates a matching GitHub release with the
same binaries and changelog.
2026-06-02 13:59:54 -04:00
lerko a5b499c247 Merge pull request 'fix(ci): make Grype CVE scan non-blocking' (#48) from fix/git-cliff-install into main
CI / test (push) Successful in 2m23s
CI / lint (push) Successful in 40s
CI / vulncheck (push) Successful in 41s
Release Binaries / release (push) Successful in 2m6s
Release Docker / docker (push) Successful in 21m22s
Reviewed-on: #48
2026-06-02 15:41:48 +00:00
lerko c963acb574 fix(ci): make Grype CVE scan non-blocking for known wish vuln
CI / test (pull_request) Successful in 2m21s
CI / lint (pull_request) Successful in 46s
CI / vulncheck (pull_request) Successful in 31s
GHSA-xjvp-7243-rg9h (wish SCP middleware path traversal) is
not exploitable — uptop only uses bubbletea middleware.
Scan still runs and warns but won't fail the release.
2026-06-02 11:24:44 -04:00
lerko c293301051 Merge pull request 'fix(ci): extract git-cliff to /tmp to avoid dirty worktree' (#47) from fix/git-cliff-install into main
CI / test (push) Successful in 2m28s
CI / lint (push) Successful in 45s
CI / vulncheck (push) Successful in 34s
Release Binaries / release (push) Successful in 2m10s
Release Docker / docker (push) Failing after 20m51s
Reviewed-on: #47
2026-06-02 14:20:23 +00:00
lerko 094de23bd0 fix(ci): extract git-cliff to /tmp to avoid dirty worktree
CI / test (pull_request) Successful in 2m29s
CI / lint (pull_request) Successful in 46s
CI / vulncheck (pull_request) Successful in 40s
GoReleaser refuses to release when untracked files exist.
The git-cliff tarball was extracting into the workspace, leaving
a git-cliff-*/ directory that made git status dirty.
2026-06-02 10:15:47 -04:00
lerko fdadafc128 Merge pull request 'fix(ci): resolve git-cliff download URL dynamically' (#46) from fix/git-cliff-install into main
CI / test (push) Successful in 2m23s
CI / lint (push) Successful in 46s
CI / vulncheck (push) Successful in 40s
Release Binaries / release (push) Failing after 28s
Release Docker / docker (push) Has been cancelled
Reviewed-on: #46
2026-06-02 14:05:18 +00:00
lerko 78fff3fb33 fix(ci): resolve git-cliff download URL dynamically
CI / test (pull_request) Successful in 2m36s
CI / lint (pull_request) Successful in 46s
CI / vulncheck (pull_request) Successful in 40s
Asset naming changed upstream — version number now embedded in
filename (git-cliff-2.13.1-x86_64-... instead of git-cliff-x86_64-...).
The latest/download shortcut 404s. Query GitHub API for current version
and build the correct URL.
2026-06-02 09:42:03 -04:00
lerko aaee3f7ebf Merge pull request 'refactor(ci): split release pipeline, add packaging and scanning' (#45) from refactor/split-release-workflows into main
CI / test (push) Successful in 2m24s
CI / lint (push) Successful in 41s
CI / vulncheck (push) Successful in 30s
Release Binaries / release (push) Failing after 22s
Release Docker / docker (push) Has been cancelled
Reviewed-on: #45
2026-06-02 13:24:37 +00:00
lerko 9b5cc37ad4 build(docker): pin base images by digest
CI / test (pull_request) Successful in 2m20s
CI / lint (pull_request) Successful in 41s
CI / vulncheck (pull_request) Successful in 41s
Prevents silently pulling a compromised or broken upstream image.
Digests must be updated manually when bumping Alpine/Go versions.
2026-06-01 21:38:31 -04:00
lerko 3a169b2bcd ci(docker): add Grype CVE scanning after image push
Scans published image for Alpine and dependency CVEs.
Fails on critical severity, reports all others in table output.
2026-06-01 21:32:20 -04:00
lerko 50eb43971c refactor(ci): split release pipeline, add nfpm/homebrew/git-cliff
Split monolithic release.yml into independent workflows:
- release-binaries.yml: tag-triggered, GoReleaser + git-cliff notes
- release-docker.yml: tag-triggered + manual dispatch, SHA tags

Add DEB/RPM packaging via nfpm in GoReleaser. Add Homebrew cask
config (skip_upload until macOS builds exist). Replace GoReleaser
built-in changelog with git-cliff for structured release notes.
2026-06-01 21:14:54 -04:00
lerko a08d6ce2c5 docs: add 2026.06.1 changelog entry
CI / test (push) Successful in 2m34s
CI / lint (push) Successful in 40s
CI / vulncheck (push) Successful in 35s
Release / release (push) Successful in 2m11s
Release / docker (push) Successful in 20m32s
2026-06-01 19:27:04 -04:00
lerko 24d4fb5e55 Merge pull request 'fix(docker): non-root user, supply chain attestations, build cleanup' (#44) from fix/docker-compliance into main
CI / test (push) Successful in 2m26s
CI / lint (push) Successful in 46s
CI / vulncheck (push) Successful in 41s
Release / docker (push) Has been cancelled
Release / release (push) Has been cancelled
Reviewed-on: #44
2026-06-01 22:48:33 +00:00
lerko 8d34524aa0 fix(docker): create .ssh dir explicitly, ensure entrypoint is executable
CI / test (pull_request) Successful in 2m31s
CI / lint (pull_request) Successful in 1m6s
CI / vulncheck (pull_request) Successful in 1m6s
2026-06-01 15:56:45 -04:00
lerko b254f6ea05 fix(docker): move SSH host key path into /data for non-root user
CI / test (pull_request) Successful in 2m26s
CI / lint (pull_request) Successful in 40s
CI / vulncheck (pull_request) Successful in 41s
2026-06-01 15:33:52 -04:00
lerko 87270490de fix(docker): non-root user, supply chain attestations, build cleanup
CI / test (pull_request) Successful in 2m29s
CI / lint (pull_request) Successful in 46s
CI / vulncheck (pull_request) Successful in 41s
BREAKING: Container now runs as UID 1000 (uptop) instead of root.
Existing volumes with root-owned files need migration:

  docker run --rm -v <volume>:/data alpine chown -R 1000:1000 /data

- Add uptop user (UID/GID 1000) with entrypoint writability check
- Enable SBOM and provenance attestations for Docker Scout compliance
- Prune dangling images and build cache after release builds
2026-06-01 11:46:05 -04:00
lerko f80e519349 Merge pull request 'ci: sync README to Docker Hub on release' (#43) from ci/dockerhub-readme into main
CI / test (push) Successful in 2m25s
CI / lint (push) Successful in 40s
CI / vulncheck (push) Successful in 31s
Release / release (push) Successful in 2m8s
Release / docker (push) Successful in 20m7s
Reviewed-on: #43
2026-05-30 23:34:56 +00:00
lerko 9a4a53f487 ci: sync README to Docker Hub on release
CI / test (pull_request) Successful in 2m23s
CI / lint (pull_request) Successful in 51s
CI / vulncheck (pull_request) Successful in 41s
Use peter-evans/dockerhub-description to push README.md as the
Docker Hub repository overview after each image build.
2026-05-29 20:51:40 -04:00
lerko 32982228b0 fix(security): patch Docker Scout CVEs and remove unused openssh-client (#41)
CI / test (push) Successful in 2m34s
CI / lint (push) Successful in 46s
CI / vulncheck (push) Successful in 40s
## Summary

- Upgrade `golang.org/x/net` v0.54.0 → v0.55.0 — patches 6 CVEs including critical CVE-2026-41589 (CVSS 9.6)
- Remove `openssh-client` from Docker image — unused (uptop uses pure Go SSH), eliminates 4 CVEs
- Add `apk upgrade` to Dockerfile for remaining Alpine package CVEs

## CVEs Resolved

| CVE | Severity | Package | Fix |
|-----|----------|---------|-----|
| CVE-2026-41589 | 9.6 Critical | golang.org/x/net | upgraded to v0.55.0 |
| CVE-2025-60876 | 6.5 Medium | golang.org/x/net | upgraded to v0.55.0 |
| CVE-2026-42502 | 6.1 Medium | golang.org/x/net | upgraded to v0.55.0 |
| CVE-2026-42506 | 6.1 Medium | golang.org/x/net | upgraded to v0.55.0 |
| CVE-2026-25681 | 6.1 Medium | golang.org/x/net | upgraded to v0.55.0 |
| CVE-2026-35414 | 6.1 Medium | golang.org/x/net | upgraded to v0.55.0 |
| CVE-2026-25680 | 7.5 High | alpine/openssh | removed openssh-client |
| CVE-2026-35386 | 3.6 Low | alpine/openssh | removed openssh-client |
| CVE-2026-35387 | 3.1 Low | alpine/openssh | removed openssh-client |
| CVE-2026-35388 | 2.5 Low | alpine/openssh | removed openssh-client |
| CVE-2026-27136 | 6.5 Medium | alpine/busybox | apk upgrade |

## Not Addressed (not exploitable)

CVE-2026-35385 (charmbracelet/wish v1.4.7, CVSS 9.6) — path traversal in wish's SCP middleware. uptop does not use the SCP middleware, only wish core + bubbletea middleware. Vulnerable code path is never loaded. Migration to wish v2 tracked in #42.

## Test Plan

- [x] `go build ./...` passes
- [x] `go test ./...` passes
- [ ] Rebuild Docker image, re-scan with Docker Scout

Reviewed-on: #41
2026-05-30 00:33:20 +00:00
lerko ec898ff943 Merge pull request 'fix(ci): use docker-builder runner for image builds' (#40) from fix/docker-release into main
CI / test (push) Successful in 2m36s
CI / lint (push) Successful in 1m11s
CI / vulncheck (push) Successful in 56s
Release / release (push) Successful in 2m28s
Release / docker (push) Successful in 26m59s
Reviewed-on: #40
2026-05-29 22:38:24 +00:00
lerko 38c7739995 fix(ci): use docker-builder runner for Docker image builds
CI / lint (pull_request) Successful in 2m14s
CI / vulncheck (pull_request) Successful in 51s
CI / test (pull_request) Successful in 3m59s
2026-05-29 18:01:07 -04:00
lerko 5679dffffa fix(ci): use internal Gitea URL for GoReleaser API calls
CI / test (push) Successful in 2m49s
CI / lint (push) Successful in 1m11s
CI / vulncheck (push) Successful in 1m1s
Release / release (push) Successful in 2m18s
Release / docker (push) Failing after 3m38s
2026-05-29 17:26:57 -04:00
lerko 9a4985e355 Merge pull request 'fix(ci): install git and gcc for GoReleaser' (#39) from fix/release-pipeline into main
CI / test (push) Successful in 2m36s
CI / lint (push) Successful in 1m22s
CI / vulncheck (push) Successful in 46s
Release / release (push) Failing after 2m12s
Release / docker (push) Has been skipped
Reviewed-on: #39
2026-05-29 20:13:01 +00:00
lerko 65406ce69c fix(ci): install git and gcc for GoReleaser in release pipeline
CI / test (pull_request) Successful in 2m49s
CI / lint (pull_request) Successful in 1m12s
CI / vulncheck (pull_request) Successful in 56s
2026-05-29 16:02:28 -04:00
lerko 2474b341ad chore: clean up dockerignore
CI / test (push) Successful in 2m36s
CI / lint (push) Successful in 1m11s
CI / vulncheck (push) Successful in 56s
Release / release (push) Failing after 30s
Release / docker (push) Has been skipped
2026-05-29 15:42:51 -04:00
lerko b0762800ac docs: update changelog for 2026.05.5
CI / test (push) Successful in 2m49s
CI / lint (push) Successful in 1m12s
CI / vulncheck (push) Successful in 46s
2026-05-29 15:37:49 -04:00
lerko 08bcdd6481 chore: move docker-compose files to deploy/
CI / test (push) Successful in 2m54s
CI / lint (push) Successful in 1m12s
CI / vulncheck (push) Successful in 56s
2026-05-29 15:30:49 -04:00
lerko ebf8bfb097 chore: add CI status badge to README
CI / test (push) Successful in 2m44s
CI / lint (push) Successful in 1m11s
CI / vulncheck (push) Successful in 46s
2026-05-29 15:17:09 -04:00
lerko b62a721277 Merge pull request 'chore: migrate module path to lerkolabs org' (#38) from chore/org-namespace into main
CI / test (push) Successful in 2m36s
CI / lint (push) Successful in 1m1s
CI / vulncheck (push) Successful in 56s
Reviewed-on: #38
2026-05-29 19:07:06 +00:00
lerko 8f17deba67 chore: migrate module path to lerkolabs org
CI / test (pull_request) Successful in 2m39s
CI / lint (pull_request) Successful in 1m6s
CI / vulncheck (pull_request) Successful in 46s
Move Go module from gitea.lerkolabs.com/lerko/uptop to
gitea.lerkolabs.com/lerkolabs/uptop. Updates all imports,
go.mod, goreleaser owner, and README links.
2026-05-29 14:22:49 -04:00
lerko 026e969b74 chore: TUI screenshots, README polish, changelog rewrite (#32)
CI / test (push) Successful in 2m41s
CI / lint (push) Successful in 1m11s
CI / vulncheck (push) Successful in 56s
- Add 6 TUI screenshots to assets/ (monitors, alerts, logs, nodes, detail, theme)
- Rewrite README with hero image, badges, collapsible install sections
- Rewrite changelog to match actual CalVer tag history
- VHS tooling extracted to lerko/uptop-vhs

Reviewed-on: lerko/uptop#32
2026-05-29 17:45:31 +00:00
lerko cfbf01274d chore(tui): visual polish — detail sections, column headers, alert detail (#37)
CI / test (push) Successful in 2m40s
CI / lint (push) Successful in 1m2s
CI / vulncheck (push) Successful in 51s
Release / docker (push) Has been cancelled
Release / release (push) Has been cancelled
## Summary

Bundled remaining UX polish items from the screenshot review.

### Changes

**Detail panel sections (#5)**
- Fields grouped into ENDPOINT, TIMING, HTTP, CONFIG sections with subtle headers
- Matches existing PROBE RESULTS and STATE CHANGES section pattern
- Cleaner visual hierarchy without box-drawing clutter

**Omit unconfigured fields (#6)**
- Timeout hidden when 0 (unconfigured)
- Method hidden when default GET
- AcceptedCodes shows "200-299" explicitly when empty

**Column header (#7)**
- `LATENCY` → `LAT` (design short, never truncate — htop/btop pattern)

**Alert detail view (#8)**
- `i` key on Alerts tab opens full detail panel
- Shows: type, health status, last sent time, send/fail counts, last error
- Full config key:value pairs (untruncated)
- Keybinding: `[i/Esc] Back  [e] Edit  [t] Test  [q] Quit`

### Files (3)
- `internal/tui/tab_sites.go` — section headers, field omission, LAT header
- `internal/tui/tab_alerts.go` — viewAlertDetailPanel()
- `internal/tui/tui.go` — stateAlertDetail, key handler, render routing

Reviewed-on: lerko/uptop#37
2026-05-28 20:40:29 +00:00
lerko 26e297cbae Merge pull request 'feat: alert channel health indicator + test alerts' (#36) from feat/alert-health into main
CI / test (push) Successful in 2m48s
CI / lint (push) Successful in 1m17s
CI / vulncheck (push) Successful in 1m6s
Reviewed-on: lerko/uptop#36
2026-05-28 01:33:00 +00:00
lerko 0aa2f9cd8a feat: alert channel health indicator + test alerts
CI / test (pull_request) Successful in 2m46s
CI / lint (pull_request) Successful in 1m1s
CI / vulncheck (pull_request) Successful in 51s
Track alert delivery health at runtime:
- AlertHealth struct: LastSendAt, LastSendOK, LastError, SendCount, FailCount
- triggerAlert records success/failure after each Send()
- Health data exposed via GetAlertHealth() for TUI

Alerts tab enriched:
- Health dot column: green (OK), red (failed), gray (never sent)
- LAST SENT column: relative time ("2m ago", "never")
- [t] key sends test notification through selected channel

Inspired by Grafana's contact point health columns.
2026-05-27 21:23:06 -04:00
lerko f17f06a1c6 Merge pull request 'feat: logs tab overhaul — severity tags, filtering, recovery durations' (#35) from feat/logs-overhaul into main
CI / test (push) Successful in 2m47s
CI / lint (push) Successful in 1m16s
CI / vulncheck (push) Successful in 56s
Reviewed-on: lerko/uptop#35
2026-05-28 00:35:24 +00:00
lerko b14d5e19db feat: logs tab overhaul — severity tags, filtering, recovery durations
CI / test (pull_request) Successful in 2m36s
CI / lint (pull_request) Successful in 1m1s
CI / vulncheck (pull_request) Successful in 51s
Logs tab visual overhaul:
- Severity-classified entries: DOWN (red), UP (green), WARN (amber),
  SYS (cyan), info (gray) — rendered as inline tags, not whole-line color
- Column-aligned format: [timestamp] [severity tag] [message]
- Filter toggle (f key): All vs Important only (hides retry noise)
- Header shows entry count, filter state, hidden count

Engine log improvements:
- Recovery messages include downtime duration ("was down 14m")
- LATE transition logged ("heartbeat overdue")
- Push monitor recovery includes downtime duration
2026-05-27 20:14:43 -04:00
lerko a2b38ddc60 Merge pull request 'feat: proper push monitor lifecycle — PENDING, LATE, DOWN' (#34) from feat/push-monitor-states into main
CI / test (push) Successful in 2m48s
CI / lint (push) Successful in 1m17s
CI / vulncheck (push) Successful in 56s
Reviewed-on: lerko/uptop#34
2026-05-28 00:01:56 +00:00
lerko 5dc31108f8 feat: proper push monitor lifecycle — PENDING, LATE, DOWN states
CI / test (pull_request) Successful in 2m41s
CI / lint (pull_request) Successful in 1m7s
CI / vulncheck (pull_request) Successful in 46s
Push monitors no longer lie about status:

- PENDING stays until first heartbeat (no auto-promote to UP)
- LATE state (amber) when overdue but within grace period
- DOWN only after grace period expires
- Grace period = interval/2, minimum 60s

RecordHeartbeat now handles all transitions:
- PENDING → UP (first heartbeat, logged)
- LATE → UP (late arrival, logged)
- DOWN → UP (recovery, alert + state change persisted)

TUI updates:
- LATE rendered in amber/warning color
- Status bar shows LATE count separately
- Tab badge shows ⚠ for late monitors
- Sort order: DOWN > LATE > UP > PENDING > PAUSED
- Detail panel shows error for LATE monitors

Inspired by Healthchecks.io state machine (new/up/grace/down).
2026-05-27 19:56:50 -04:00
lerko 63773b13d0 Merge pull request 'feat: show error reason when monitors go DOWN' (#33) from feat/error-reason into main
CI / test (push) Successful in 2m51s
CI / lint (push) Successful in 1m1s
CI / vulncheck (push) Successful in 56s
Reviewed-on: lerko/uptop#33
2026-05-27 23:38:26 +00:00
lerko bc3a44beac feat: show error reason when monitors go DOWN
CI / test (pull_request) Successful in 2m42s
CI / lint (pull_request) Successful in 1m11s
CI / vulncheck (pull_request) Successful in 51s
Propagate check failure reasons through the entire stack:
- Checker captures specific errors (DNS, timeout, HTTP status, SSL, etc.)
- Engine tracks LastError, StatusChangedAt, LastSuccessAt per monitor
- State transitions persisted to new state_changes table
- Detail panel shows error reason, HTTP code, state duration, last
  success time, and last 5 state change events
- Monitor table shows inline error preview for DOWN services
- Alert messages include error reason
- Probe nodes forward error reasons to leader

15 files changed across models, checker, engine, store, TUI, and probes.
2026-05-27 19:32:30 -04:00
lerko d8a2cab90f feat: seed SSH users from env var and authorized_keys file (#31)
CI / test (push) Successful in 2m36s
CI / lint (push) Successful in 1m12s
CI / vulncheck (push) Successful in 56s
Release / release (push) Has been cancelled
Release / docker (push) Has been cancelled
## Summary

Docker onboarding was broken — no way to add first SSH user without `docker attach` to TUI.

Now reads SSH public keys from two sources on startup:
- `UPTOP_ADMIN_KEY` env var — single key for quick single-user setup
- `UPTOP_KEYS` file path — authorized_keys format for team setup

Dockerfile already sets `UPTOP_KEYS=/data/authorized_keys` and compose mounts `./data:/data`, so the flow is:

```
echo "ssh-ed25519 AAAA... me@host" > ./data/authorized_keys
docker compose up -d
ssh -p 23234 localhost
```

### Behavior
- Skips keys already in DB (idempotent across restarts)
- All seeded users get admin role
- Username parsed from key comment (e.g. `tyler@macbook` → `tyler`)
- Comments and blank lines in keys file are ignored

### Tested
- UPTOP_ADMIN_KEY seeds single admin user
- UPTOP_KEYS file seeds multiple users with correct usernames
- Second startup skips existing keys (no duplicates)
- Build and all tests pass

Reviewed-on: lerko/uptop#31
2026-05-27 21:15:00 +00:00
lerko ea721601ab Merge pull request 'ci: overhaul pipeline — caching, GoReleaser, govulncheck' (#30) from ci/pipeline-overhaul into main
CI / test (push) Successful in 2m50s
CI / lint (push) Successful in 1m11s
CI / vulncheck (push) Successful in 56s
Reviewed-on: lerko/uptop#30
2026-05-27 00:37:32 +00:00
lerko b1935aa682 fix(deps): bump golang.org/x/crypto v0.47.0 → v0.52.0
CI / test (pull_request) Successful in 2m46s
CI / lint (pull_request) Successful in 1m12s
CI / vulncheck (pull_request) Successful in 56s
Fixes 7 vulns (GO-2026-5014 through GO-2026-5023) found by govulncheck.
Also bumps x/net, x/sys, x/text, x/sync, x/mod, x/tools to latest.
2026-05-26 20:20:23 -04:00
lerko 2cd3dcddb4 chore: bump Go 1.24.4 → 1.26.3, Alpine 3.21 → 3.23
CI / test (pull_request) Successful in 2m57s
CI / lint (pull_request) Successful in 1m11s
CI / vulncheck (pull_request) Failing after 1m1s
Go 1.24 EOL since Feb 2026. Fixes 33 stdlib vulns found by
govulncheck (database/sql, os/exec, net/http). Gets Green Tea GC.
2026-05-26 20:12:43 -04:00