6 Commits

Author SHA1 Message Date
lerko dbd519c121 fix: 4 additional release-consistency findings
CI / test (pull_request) Successful in 1m46s
CI / lint (pull_request) Successful in 1m11s
CI / vulncheck (pull_request) Successful in 51s
- Disable healthcheck on probe compose services (no HTTP server)
- Remove stale "(Phase 4)" comment from dev compose
- Add data/ to .gitignore (compose volume creates deploy/data)
- Clarify -db-type flag help text (sqlite or postgres)
2026-06-19 20:37:42 -04:00
lerko b32145fb58 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
2026-06-19 20:09:03 -04:00
lerko 47d3b0e68f fix(tui): bump Subtle ANSI fallback from "8" to "7"
CI / test (pull_request) Successful in 1m49s
CI / lint (pull_request) Successful in 1m12s
CI / vulncheck (pull_request) Successful in 56s
Bright black ("8") plus Faint made PENDING status and dividers nearly
invisible in 16-color terminals. White ("7") with Faint renders as a
readable dim gray while still sitting below Muted in the hierarchy.
2026-06-19 17:27:54 -04:00
lerko 8fd13fefbf feat(tui): add monochrome emphasis attributes for SSH readability
Apply Bold/Faint attributes to semantic styles following htop's
monochrome design principle. Creates 4-tier visual hierarchy that
works even when colors collapse: Bold (danger/warn), Normal (success/
default), Faint (subtle/stale/borders/inactive tabs). Complements
the ANSI-16 color fallbacks without affecting TrueColor appearance.
2026-06-19 17:27:54 -04:00
lerko 974c4b61ea fix(tui): add ANSI-16 color fallbacks for SSH terminals
Theme colors now use lipgloss.CompleteColor with hand-picked ANSI-16
values instead of raw hex. Prevents algorithmic degradation from
collapsing dark backgrounds into indistinguishable ANSI colors over
SSH. Backgrounds fall through to terminal default in 16-color mode;
semantic colors map to distinct ANSI indices (green/yellow/red/blue/
cyan/magenta). TrueColor rendering is unchanged.
2026-06-19 17:27:54 -04:00
lerko d50a5159d4 fix(release): pin GoReleaser to triggering tag
CI / test (pull_request) Successful in 1m43s
CI / lint (pull_request) Successful in 1m22s
CI / vulncheck (pull_request) Successful in 56s
GORELEASER_CURRENT_TAG prevents GoReleaser from resolving the
wrong tag via git-describe when multiple tags point to the same
commit (e.g. v0.1.0 + v0.1.0-rc.5 on adf8fed).
2026-06-17 17:26:16 -04:00
14 changed files with 56 additions and 25 deletions
+3 -1
View File
@@ -1,5 +1,5 @@
# ─── uptop configuration ─────────────────────────────────── # ─── 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 ────────────────────────────────────────────────── # ─── Core ──────────────────────────────────────────────────
UPTOP_PORT=23234 # SSH server port 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_ALLOW_PRIVATE_TARGETS=false # Allow monitoring RFC1918/loopback addresses
# UPTOP_METRICS_PUBLIC=false # Expose /metrics without auth # UPTOP_METRICS_PUBLIC=false # Expose /metrics without auth
# UPTOP_CORS_ORIGIN= # Access-Control-Allow-Origin for /status/json # 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? label: What happened?
description: Include what you expected to happen instead. description: Include what you expected to happen instead.
placeholder: | 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. I expected it to keep running and display monitor status.
validations: validations:
required: true required: true
@@ -25,7 +25,7 @@ body:
attributes: attributes:
label: Steps to reproduce label: Steps to reproduce
placeholder: | placeholder: |
1. Run `uptop serve` 1. Run `uptop`
2. Wait ~10 seconds 2. Wait ~10 seconds
3. TUI crashes with panic 3. TUI crashes with panic
validations: validations:
@@ -37,7 +37,7 @@ body:
description: Output of `uptop version`, OS, terminal. Paste any errors below. description: Output of `uptop version`, OS, terminal. Paste any errors below.
render: shell render: shell
placeholder: | placeholder: |
uptop version 2026.06.1 uptop 0.1.0 (abc1234, 2026-06-17)
OS: Debian 13 OS: Debian 13
Terminal: Ghostty Terminal: Ghostty
+1
View File
@@ -49,6 +49,7 @@ jobs:
version: "~> v2" version: "~> v2"
args: release --clean --release-notes=/tmp/release-notes.md args: release --clean --release-notes=/tmp/release-notes.md
env: env:
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
GORELEASER_FORCE_TOKEN: gitea GORELEASER_FORCE_TOKEN: gitea
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITEA_API_URL: http://gitea:3000/api/v1 GITEA_API_URL: http://gitea:3000/api/v1
+1
View File
@@ -25,4 +25,5 @@ authorized_keys
tmp tmp
*.local.json *.local.json
*.local.md *.local.md
data/
.env .env
+1 -2
View File
@@ -23,14 +23,13 @@ RUN mkdir -p /data/.ssh && chown -R uptop:uptop /data
COPY --from=builder /app/uptop . COPY --from=builder /app/uptop .
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/ COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/
ENV LIPGLOSS_RENDERER_HAS_DARK_BACKGROUND=true
ENV UPTOP_DB_TYPE=sqlite ENV UPTOP_DB_TYPE=sqlite
ENV UPTOP_DB_DSN=/data/uptop.db ENV UPTOP_DB_DSN=/data/uptop.db
ENV UPTOP_KEYS=/data/authorized_keys ENV UPTOP_KEYS=/data/authorized_keys
ENV UPTOP_SSH_HOST_KEY=/data/.ssh/id_ed25519 ENV UPTOP_SSH_HOST_KEY=/data/.ssh/id_ed25519
ENV UPTOP_PORT=23234 ENV UPTOP_PORT=23234
EXPOSE 23234 EXPOSE 8080 23234
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:8080/api/health || exit 1 CMD wget -qO- http://localhost:8080/api/health || exit 1
USER uptop 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>No browser. No client install. Just <code>ssh -p 23234 your-server</code>.</p>
<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/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/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"> <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 - **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 - **Config as code** — define monitors in YAML, apply declaratively, version control your setup
- **HA clustering** — leader/follower with automatic failover - **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 - **Public status page** — HTML + JSON, toggle with an env var
- **SQLite or Postgres** — SQLite for single-node, Postgres for production - **SQLite or Postgres** — SQLite for single-node, Postgres for production
- **Uptime Kuma import** — migrate from Kuma with one command - **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_INSECURE_SKIP_VERIFY` | `false` | Skip TLS verification for checks |
| `UPTOP_ALLOW_PRIVATE_TARGETS` | `false` | Allow monitoring RFC1918/loopback addresses | | `UPTOP_ALLOW_PRIVATE_TARGETS` | `false` | Allow monitoring RFC1918/loopback addresses |
| `UPTOP_ADMIN_KEY` | | SSH public key seeded as first admin on startup | | `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)) | | `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. 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 | | Check history | newest 1,000 checks per monitor |
| State changes (UP/DOWN transitions) | newest 5,000 per monitor | | State changes (UP/DOWN transitions) | newest 5,000 per monitor |
| Logs | newest 200 entries | | 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. 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.
+25 -5
View File
@@ -77,17 +77,37 @@ func main() {
case "export": case "export":
runExport(os.Args[2:]) runExport(os.Args[2:])
return return
case "version", "--version", "-v": case "version", "--version", "-v", "-version":
printVersion() printVersion()
return return
case "migrate-secrets": case "migrate-secrets":
runMigrateSecrets(os.Args[2:]) runMigrateSecrets(os.Args[2:])
return return
case "help", "--help", "-h":
printUsage()
return
case "serve":
runServe(os.Args[2:])
return
} }
} }
runServe(os.Args[1:]) 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() { func printVersion() {
out := "uptop " + version out := "uptop " + version
var meta []string var meta []string
@@ -186,7 +206,7 @@ func runApply(args []string) {
filePath := fs.String("f", "", "Path to YAML config file (required)") filePath := fs.String("f", "", "Path to YAML config file (required)")
dryRun := fs.Bool("dry-run", false, "Show planned changes without applying") dryRun := fs.Bool("dry-run", false, "Show planned changes without applying")
prune := fs.Bool("prune", false, "Delete monitors/alerts not in YAML") prune := fs.Bool("prune", false, "Delete monitors/alerts not in YAML")
dbType := fs.String("db-type", envOrDefault("UPTOP_DB_TYPE", "sqlite"), "Database type") dbType := fs.String("db-type", envOrDefault("UPTOP_DB_TYPE", "sqlite"), "Database type (sqlite or postgres)")
dsn := fs.String("dsn", envOrDefault("UPTOP_DB_DSN", "uptop.db"), "Database DSN") dsn := fs.String("dsn", envOrDefault("UPTOP_DB_DSN", "uptop.db"), "Database DSN")
_ = fs.Parse(args) // ExitOnError: parse errors exit before returning _ = fs.Parse(args) // ExitOnError: parse errors exit before returning
@@ -219,7 +239,7 @@ func runApply(args []string) {
func runExport(args []string) { func runExport(args []string) {
fs := flag.NewFlagSet("export", flag.ExitOnError) fs := flag.NewFlagSet("export", flag.ExitOnError)
outPath := fs.String("o", "-", "Output file path (- for stdout)") outPath := fs.String("o", "-", "Output file path (- for stdout)")
dbType := fs.String("db-type", envOrDefault("UPTOP_DB_TYPE", "sqlite"), "Database type") dbType := fs.String("db-type", envOrDefault("UPTOP_DB_TYPE", "sqlite"), "Database type (sqlite or postgres)")
dsn := fs.String("dsn", envOrDefault("UPTOP_DB_DSN", "uptop.db"), "Database DSN") dsn := fs.String("dsn", envOrDefault("UPTOP_DB_DSN", "uptop.db"), "Database DSN")
_ = fs.Parse(args) // ExitOnError: parse errors exit before returning _ = fs.Parse(args) // ExitOnError: parse errors exit before returning
@@ -239,7 +259,7 @@ func runExport(args []string) {
func runMigrateSecrets(args []string) { func runMigrateSecrets(args []string) {
fs := flag.NewFlagSet("migrate-secrets", flag.ExitOnError) fs := flag.NewFlagSet("migrate-secrets", flag.ExitOnError)
dbType := fs.String("db-type", envOrDefault("UPTOP_DB_TYPE", "sqlite"), "Database type") dbType := fs.String("db-type", envOrDefault("UPTOP_DB_TYPE", "sqlite"), "Database type (sqlite or postgres)")
dsn := fs.String("dsn", envOrDefault("UPTOP_DB_DSN", "uptop.db"), "Database DSN") dsn := fs.String("dsn", envOrDefault("UPTOP_DB_DSN", "uptop.db"), "Database DSN")
_ = fs.Parse(args) _ = fs.Parse(args)
@@ -331,7 +351,7 @@ func runServe(args []string) {
fs := flag.NewFlagSet("serve", flag.ExitOnError) fs := flag.NewFlagSet("serve", flag.ExitOnError)
port := fs.Int("port", cfg.Port, "SSH Port") port := fs.Int("port", cfg.Port, "SSH Port")
flagDBType := fs.String("db-type", cfg.DBType, "Database type") flagDBType := fs.String("db-type", cfg.DBType, "Database type (sqlite or postgres)")
flagDSN := fs.String("dsn", cfg.DBDSN, "Database DSN") flagDSN := fs.String("dsn", cfg.DBDSN, "Database DSN")
demo := fs.Bool("demo", false, "Seed demo data") demo := fs.Bool("demo", false, "Seed demo data")
importKuma := fs.String("import-kuma", "", "Import Uptime Kuma backup JSON file") importKuma := fs.String("import-kuma", "", "Import Uptime Kuma backup JSON file")
+4
View File
@@ -5,6 +5,8 @@ services:
leader: leader:
image: lerkolabs/uptop:latest image: lerkolabs/uptop:latest
container_name: uptop-leader container_name: uptop-leader
sysctls:
- net.ipv4.ping_group_range=0 2147483647
ports: ports:
- "23234:23234" # SSH - "23234:23234" # SSH
- "8080:8080" # HTTP - "8080:8080" # HTTP
@@ -40,6 +42,8 @@ services:
follower: follower:
image: lerkolabs/uptop:latest image: lerkolabs/uptop:latest
container_name: uptop-follower container_name: uptop-follower
sysctls:
- net.ipv4.ping_group_range=0 2147483647
ports: ports:
- "23233:23234" # SSH (Mapped to different host port) - "23233:23234" # SSH (Mapped to different host port)
- "8081:8080" # HTTP (Mapped to different host port) - "8081:8080" # HTTP (Mapped to different host port)
+1 -1
View File
@@ -13,7 +13,7 @@ services:
- UPTOP_DB_TYPE=postgres - UPTOP_DB_TYPE=postgres
- UPTOP_DB_DSN=postgres://devuser:devpass@postgres:5432/uptop_dev?sslmode=disable - UPTOP_DB_DSN=postgres://devuser:devpass@postgres:5432/uptop_dev?sslmode=disable
# --- Web Server Configuration (Phase 4) --- # --- Web Server Configuration ---
- UPTOP_HTTP_PORT=8080 - UPTOP_HTTP_PORT=8080
- UPTOP_STATUS_ENABLED=true - UPTOP_STATUS_ENABLED=true
- UPTOP_STATUS_TITLE=Dev Infrastructure Status - UPTOP_STATUS_TITLE=Dev Infrastructure Status
+6
View File
@@ -1,6 +1,8 @@
services: services:
leader: leader:
image: lerkolabs/uptop:latest image: lerkolabs/uptop:latest
sysctls:
- net.ipv4.ping_group_range=0 2147483647
environment: environment:
- UPTOP_CLUSTER_MODE=leader - UPTOP_CLUSTER_MODE=leader
- UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use - UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use
@@ -12,6 +14,8 @@ services:
probe-us-east: probe-us-east:
image: lerkolabs/uptop:latest image: lerkolabs/uptop:latest
healthcheck:
disable: true
environment: environment:
- UPTOP_CLUSTER_MODE=probe - UPTOP_CLUSTER_MODE=probe
- UPTOP_NODE_ID=us-east-1 - UPTOP_NODE_ID=us-east-1
@@ -24,6 +28,8 @@ services:
probe-eu-west: probe-eu-west:
image: lerkolabs/uptop:latest image: lerkolabs/uptop:latest
healthcheck:
disable: true
environment: environment:
- UPTOP_CLUSTER_MODE=probe - UPTOP_CLUSTER_MODE=probe
- UPTOP_NODE_ID=eu-west-1 - UPTOP_NODE_ID=eu-west-1
+2
View File
@@ -8,6 +8,8 @@ services:
- ALL - ALL
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
sysctls:
- net.ipv4.ping_group_range=0 2147483647
tmpfs: tmpfs:
- /tmp - /tmp
ports: ports:
+2 -4
View File
@@ -47,13 +47,11 @@ Probes are lightweight, stateless nodes that run checks from different locations
| Node | Variable | Value | | Node | Variable | Value |
|------|----------|-------| |------|----------|-------|
| Both | `UPTOP_CLUSTER_SECRET` | Same shared secret | | 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_CLUSTER_MODE` | `probe` |
| Probe | `UPTOP_PEER_URL` | Leader's HTTP URL | | Probe | `UPTOP_PEER_URL` | Leader's HTTP URL |
| Probe | `UPTOP_NODE_ID` | Unique identifier (e.g. `probe-us-east`) | | 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. 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 ## 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. - 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. - 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) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return 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.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK")) _, _ = w.Write([]byte("OK"))
} }
+2 -2
View File
@@ -219,8 +219,8 @@ func TestHealth_WrongSecret(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 401 { if resp.StatusCode != 200 {
t.Errorf("expected 401, got %d", resp.StatusCode) t.Errorf("health is unauthenticated, expected 200, got %d", resp.StatusCode)
} }
} }