Skip to content

Pin panel versions, report runtime status, fix log preconditions - #6

Merged
igun997 merged 4 commits into
masterfrom
feat/panel-version-pinning-and-runtime-status
Aug 6, 2026
Merged

Pin panel versions, report runtime status, fix log preconditions#6
igun997 merged 4 commits into
masterfrom
feat/panel-version-pinning-and-runtime-status

Conversation

@igun997

@igun997 igun997 commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Prepares v0.1.0. Started from two live failures on panel.srv-opc.ciptadusa.com (Easypanel 2.33.0): GetLogs returning [BAD_REQUEST] fetch failed, and GetServiceStatus returning almost nothing for a compose service.

Why GetLogs failed

Panel-side, not ours. Extracted app/backend.js from the official images:

queryComposeServiceLogs: ... handler -> Voe(query)      // fetch(LOKI_URL + /loki/api/v1/query_range)
function wDe(t){ return (process.env.LOKI_URL ?? `http://easypanel-loki:3100`) + t }

Easypanel serves logs only from its own Loki deployment; there is no docker logs route anywhere in the panel API. Loki and Promtail are deployed only when log aggregation is enabled, which the panel gates behind a license with advanced_monitoring. On this panel logs.getSettings returns null and no license is present, so the panel's own fetch dies and gets wrapped as BAD_REQUEST: fetch failed.

GetLogs now prechecks and explains:

FailedPrecondition: panel log aggregation is disabled, so Loki is not deployed and the
panel has no log source. Enable it in the Easypanel UI under Settings -> Logs
(requires a license with advanced monitoring), then retry GetLogs.

Enabled-but-unreachable Loki is reported separately. Also added the panel's remaining filters (stream, levels, search, start, end), limit clamping to the panel maximum of 1000, and local InvalidArgument for bad stream values.

Why GetServiceStatus was sparse

inspectService returns stored config only, and compose services keep source.content rather than an image, so the response had no image and no runtime signal. Now merged with the panel's Docker view via projects.getDockerContainers and services.compose.getDockerServices:

{
  "status": "running",              // running | stopped | unknown
  "runningContainers": 1,
  "containers": [{ "name": "pods_hermes-a1-agent-1", "image": "nousresearch/hermes-agent:v2026.8.3",
                   "state": "running", "status": "Up 47 hours" }],
  "composeServices": ["agent"]
}

A refused container query degrades to status: "unknown" with configuration intact instead of failing the RPC.

Version pinning

Pulled three separate images and diffed their route surfaces:

version procedures diff vs 2.33.1
2.32.2 375 none
2.33.0 375 none
2.33.1 (= latest) 375

Identical route names and input fields, so one code path covers 2.32.0..2.33.1. Surfaces are pinned in internal/easypanel/testdata/, every route used is declared in internal/easypanel/routes.go, and tests assert each one still exists with an unchanged input shape. grpc-server probes info.version from /api/openapi.json (the only version route present in all supported releases; /api/cli.json and /api/mcp are 2.33+) and logs whether the panel is inside the window. scripts/panel-surface.sh re-pins after an upgrade.

Breaking change

GetServiceStatus no longer returns env values by default. The panel stores secrets in the env blob its inspect routes return, and a GRPC_AUTH_TOKENS entry can read every project — the live pods/deploy-everything response contained its own EASYPANEL_API_KEY and GRPC_AUTH_TOKENS. Keys are kept, values become <redacted>; set include_env: true for clear text.

Verification

  • Live e2e against the panel: AuthReject, DeployContainerE2E, DeployComposeE2E, UpdateResources, plus new RuntimeStatusAndLogs (asserts running container payload, the log precondition, local stream rejection, and env redaction on/off).
  • Unit tests with a fake panel: log filter forwarding, limit clamping, precondition and unreachable-store mapping, runtime status assembly, env redaction, version parsing, panel version probe, route surface compatibility.
  • make fmt-check, go vet ./..., go test ./... -count=1 all clean.

Follow-up

Release is not cut yet: tagging v0.1.0 is what publishes images and binaries.

Summary by CodeRabbit

  • New Features

    • Service status now includes runtime state, container details, running counts, and Compose services.
    • Environment values are redacted by default, with an option to include them when needed.
    • Log retrieval supports stream, level, search, time-range, and result-limit filters.
    • Added commands to view panel version compatibility and log aggregation settings.
    • Startup can optionally skip the panel version check.
  • Bug Fixes

    • Improved log limit validation and clearer guidance for unavailable log aggregation.
  • Documentation

    • Updated deployment guidance, supported versions, release notes, and troubleshooting instructions.

igun997 added 3 commits August 6, 2026 20:09
The panel API is the whole contract here, and it moves between releases. Two
things were missing: a single place listing the routes this project calls, and
any signal when the panel in front of us is outside the range those routes were
verified against.

Route surfaces are now extracted from the official easypanel/easypanel images
and pinned per release in testdata. Across 2.32.2, 2.33.0 and 2.33.1 the panel
exposes the same 375 procedures with identical input fields, which is why one
code path covers 2.32.0..2.33.1.

- internal/easypanel/routes.go declares every route, replacing scattered
  string literals; RequiredRoutes drives the compatibility test.
- Tests assert each route exists in every pinned release and that its input
  shape is unchanged across them.
- Client.PanelVersion reads info.version from /api/openapi.json, the one version
  route present in all supported releases (/api/cli.json is 2.33+ only).
- grpc-server logs the panel version and support state at startup;
  EASYPANEL_SKIP_VERSION_CHECK=1 skips the probe.
- New CLI commands: settings panel-version, settings logs.
- scripts/panel-surface.sh re-pins surfaces after a panel upgrade.
Three gaps showed up while checking every RPC against a live panel.

GetLogs returned the panel's opaque "[BAD_REQUEST] fetch failed". Easypanel
serves logs only from its own Loki deployment (queryServiceLogs fetches
LOKI_URL/loki/api/v1/query_range) and exposes no docker-logs route at all. Loki
and Promtail exist only when log aggregation is enabled, a licensed feature. The
RPC now prechecks logs.getSettings and returns FailedPrecondition naming the
cause and the fix, and separately reports an enabled-but-unreachable log store.
It also forwards the panel's remaining filters (stream, levels, search, start,
end), clamps limit to the panel maximum of 1000 instead of tripping validation,
and rejects bad stream values locally.

GetServiceStatus returned stored configuration only, so a compose service came
back with no image and no indication of whether it was running. It now merges the
panel's Docker view: status (running/stopped/unknown), running_containers,
containers[], and compose_services[] for compose. A refused container query
degrades to unknown rather than failing the call.

BREAKING: GetServiceStatus no longer returns env values by default. The panel
stores secrets in the same env blob its inspect routes return, and a
GRPC_AUTH_TOKENS entry can read every project, so keys are kept and values become
<redacted>. Set include_env on the request for clear text.
- CHANGELOG.md with the v0.1.0 entry, including the GetServiceStatus env
  behaviour change and the supported Easypanel range.
- README: supported panel versions and how to re-pin them, service status fields,
  env redaction, log requirements, unit vs live test commands, 0.1.0 image tags.
- Deployment guide: status/runtime fields, include_env, log filters and the
  licensed-Loki requirement, panel-version troubleshooting, 0.1.0 examples.
- .env.example: EASYPANEL_SKIP_VERSION_CHECK.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@igun997, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7aeea35a-bb19-4dde-9fe7-4dc06fedd4b8

📥 Commits

Reviewing files that changed from the base of the PR and between 0bb21c8 and 63ddb42.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • README.md
  • cmd/grpc-server/main.go
  • cmd/grpc-server/main_test.go
  • docs/deployment-guide.md
  • internal/easypanel/routes_test.go
  • internal/easypanel/testdata/panel-surface-2.32.0.json
  • internal/easypanel/testdata/panel-surface-2.32.2.json
  • internal/easypanel/testdata/panel-surface-2.33.0.json
  • internal/easypanel/testdata/panel-surface-2.33.1.json
  • internal/easypanel/version.go
  • internal/server/getlogs_test.go
  • internal/server/paas.go
  • internal/server/panel_fake_test.go
  • scripts/extract-panel-surface.py
📝 Walkthrough

Walkthrough

The change adds Easypanel route-surface validation, panel version and log probes, runtime container status, environment redaction, filtered logs, CLI commands, release documentation, and unit and end-to-end tests.

Changes

Panel compatibility and runtime observability

Layer / File(s) Summary
Centralized routes and pinned panel surfaces
internal/easypanel/routes.go, internal/easypanel/routes_test.go, internal/easypanel/testdata/*, scripts/*
Centralized route helpers define the required Easypanel surface. Extraction scripts generate versioned procedure fixtures. Tests compare routes, inputs, and support ranges across pinned panel versions.
Panel version and log settings probes
internal/easypanel/client.go, internal/easypanel/version.go, internal/easypanel/version_test.go
The client parses and caches panel versions and log aggregation settings. It classifies known Loki failures.
Runtime status, environment, and log RPC behavior
proto/paas.proto, internal/server/paas.go, internal/server/env.go, internal/server/logs.go, internal/server/*_test.go
RPCs expose container status and Compose services, redact environment values by default, support opt-in clear text, validate log requests, forward filters, and map aggregation failures.
Startup checks, CLI commands, and release documentation
cmd/*, .env.example, README.md, CHANGELOG.md, docs/deployment-guide.md, grpc_e2e_test.go
Startup checks the panel version unless disabled. Settings commands report log and version state. Documentation and E2E coverage describe and validate the new behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GRPCClient
  participant PaaSServer
  participant Easypanel
  participant Docker
  GRPCClient->>PaaSServer: Request runtime status or logs
  PaaSServer->>Easypanel: Query service or log data
  PaaSServer->>Docker: Query container runtime data
  Docker-->>PaaSServer: Return container details
  Easypanel-->>PaaSServer: Return panel data
  PaaSServer-->>GRPCClient: Return status, redacted environment, or logs
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes three primary changes: panel version pinning, runtime status reporting, and improved log preconditions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/panel-version-pinning-and-runtime-status

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (8)
internal/easypanel/routes.go (1)

25-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the stale identifier in the comment.

The comment names appOnlyProcedures, but the variable at Line 66 is typeOnlyProcedures.

📝 Proposed fix
 // Per-type procedure names. Not every service router exposes all of them;
-// appOnlyProcedures records the ones that only exist under services.app.
+// typeOnlyProcedures records the ones that exist under a single service router.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/easypanel/routes.go` around lines 25 - 26, Update the comment
describing the per-type procedure names to reference typeOnlyProcedures instead
of the stale appOnlyProcedures identifier, matching the variable declared near
the router definitions.
scripts/extract-panel-surface.py (2)

11-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Close the file handles.

load and json.dump leave file objects open until garbage collection. Use with blocks. Also validate sys.argv length so a missing argument reports usage instead of IndexError.

♻️ Proposed refactor
 def load(path):
-    return open(path, encoding="utf8", errors="replace").read()
+    with open(path, encoding="utf8", errors="replace") as fh:
+        return fh.read()
-    json.dump(out, open(sys.argv[2], "w"), indent=1, sort_keys=True)
+    with open(sys.argv[2], "w", encoding="utf8") as fh:
+        json.dump(out, fh, indent=1, sort_keys=True)

Also applies to: 241-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/extract-panel-surface.py` around lines 11 - 12, Update load to open
the file through a with block so its handle is closed deterministically, and
apply the same context-manager pattern to the json.dump file operation. Add
sys.argv length validation before argument access, reporting usage and exiting
cleanly when the required path argument is missing.

120-120: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused SCHEMA_ALIAS constant.

No code reads or writes SCHEMA_ALIAS.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/extract-panel-surface.py` at line 120, Remove the unused SCHEMA_ALIAS
constant declaration, ensuring no other code or behavior is changed.
scripts/panel-surface.sh (1)

32-36: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Remove the container if a step fails.

set -e aborts the loop if docker export, tar, or the python steps fail. The docker rm on Line 36 then never runs, and the created container stays on the host. Register cleanup right after docker create.

♻️ Proposed refactor
   container="ep-surface-${version//./-}-$$"
   docker create --name "$container" "$IMAGE:$version" >/dev/null
+  trap 'docker rm -f "$container" >/dev/null 2>&1 || true; rm -rf "$WORK"' EXIT
   # Only app/backend.js is needed; the rest of the image is ~900MB.
   docker export "$container" | tar -x -C "$WORK" app/backend.js app/package.json
   docker rm "$container" >/dev/null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/panel-surface.sh` around lines 32 - 36, Register cleanup immediately
after the docker create command for the container created in the panel-surface
extraction flow, so failures in docker export, tar, or subsequent processing
still remove it. Ensure cleanup runs on script exit while preserving the
existing explicit docker rm behavior without attempting unsafe duplicate
removal.
internal/easypanel/routes_test.go (1)

130-141: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use strings.Join in the join helper.

The manual loop repeats standard-library behavior. sort.Strings plus strings.Join is shorter and avoids repeated string allocation.

♻️ Proposed refactor
 func join(in []string) string {
 	sorted := append([]string(nil), in...)
 	sort.Strings(sorted)
-	out := ""
-	for i, s := range sorted {
-		if i > 0 {
-			out += ","
-		}
-		out += s
-	}
-	return out
+	return strings.Join(sorted, ",")
 }

Add "strings" to the import block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/easypanel/routes_test.go` around lines 130 - 141, Update the join
helper to retain the sorted copy from sort.Strings and return
strings.Join(sorted, ",") instead of manually concatenating elements; add the
strings import required for this standard-library helper.
internal/server/getlogs_test.go (1)

16-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the fail-open path of the aggregation precheck.

GetLogs only rejects the request when LogAggregation returns no error. When the settings probe itself fails, the code continues to the query route. No test covers that branch. A test that leaves easypanel.RouteLogsGetSettings unstubbed would pin this intentional fail-open behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/server/getlogs_test.go` around lines 16 - 36, Add a test alongside
TestGetLogsRejectsDisabledLogAggregation that leaves
easypanel.RouteLogsGetSettings unstubbed, invokes GetLogs with the same request,
and verifies the settings-probe error does not prevent the query route from
being called. Assert the intentional fail-open outcome while keeping the
existing disabled-aggregation test unchanged.
internal/server/panel_fake_test.go (1)

56-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Honor the status code parsed from the !<status> prefix.

The fixture format documents a status code, but handle discards parts[0] and always writes 400. A future fixture such as !500 {...} would still return 400, and a test that asserts on server-side status mapping would pass for the wrong reason.

♻️ Proposed fix
 	if strings.HasPrefix(resp, "!") {
 		// "!<status> <body>" returns an error response.
 		parts := strings.SplitN(strings.TrimPrefix(resp, "!"), " ", 2)
-		w.WriteHeader(http.StatusBadRequest)
+		code, err := strconv.Atoi(parts[0])
+		if err != nil {
+			p.t.Errorf("fake panel: invalid status prefix %q for route %s", parts[0], route)
+			code = http.StatusBadRequest
+		}
+		w.WriteHeader(code)
 		if len(parts) == 2 {
 			_, _ = w.Write([]byte(parts[1]))
 		}
 		return
 	}

Add "strconv" to the imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/server/panel_fake_test.go` around lines 56 - 64, The error-response
branch in handle must use the status code encoded in parts[0] rather than always
returning http.StatusBadRequest. Parse parts[0] with strconv, write the parsed
status when valid, and preserve the existing bad-request fallback for malformed
or missing status values.
internal/server/paas.go (1)

379-381: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Gate include_env with an authorization scope before returning clear-text env.

IncludeEnv exposes Env values without any check beyond auth.Interceptor, while auth.Interceptor only validates token membership. If these gRPC tokens are shared across callers, consider a dedicated scope or permission for clear-text environment variables, or keep defaults enforced per token type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/server/paas.go` around lines 379 - 381, Update the response handling
around IncludeEnv in the relevant server method so clear-text environment values
are returned only when the caller has an explicit authorization scope or
permission for environment access. Reuse the existing auth/interceptor
authorization model and preserve redactEnvValues as the default for unauthorized
or absent permission, rather than relying solely on token membership.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/grpc-server/main.go`:
- Around line 41-46: Update the EASYPANEL_SKIP_VERSION_CHECK condition around
reportPanelVersion so the probe is skipped only when the environment value
equals "1"; run reportPanelVersion for unset, "0", and all other values.

In `@docs/deployment-guide.md`:
- Around line 8-11: Mark the v0.1.0 installation examples as pending until the
release artifacts exist, and update every affected reference to use an available
published tag instead of 0.1.0/v0.1.0. In docs/deployment-guide.md#L8-L11,
docs/deployment-guide.md#L64-L78, docs/deployment-guide.md#L87-L87,
docs/deployment-guide.md#L573-L580, docs/deployment-guide.md#L595-L595, and
docs/deployment-guide.md#L886-L889, adjust the release/image examples
accordingly; in README.md#L504-L510, do the same for the server and CLI image
tags.

In `@internal/easypanel/version.go`:
- Around line 14-22: Align the supported version floor with pinned coverage: in
internal/easypanel/version.go lines 14-22, either set MinSupportedVersion to
2.32.2 or add a 2.32.0 fixture while correcting the surrounding comment; in
scripts/panel-surface.sh lines 18-22, update the default VERSIONS list to start
at the same chosen floor and keep its comment accurate.
- Line 110: Update the deferred response-body cleanup in the surrounding HTTP
request function to explicitly discard the return value from resp.Body.Close,
satisfying errcheck while preserving the existing cleanup behavior.

In `@internal/server/paas.go`:
- Around line 798-803: Update the GetLogs precheck around LogAggregation so a
disabled or unavailable aggregation result invalidates the cached log settings
before returning FailedPrecondition, allowing a retry to observe newly enabled
aggregation without waiting for the TTL. Preserve the existing enabled-settings
path and error response behavior.

In `@README.md`:
- Around line 422-426: Update the fenced code block containing the panel log
aggregation error text to specify the text language identifier, preserving its
existing content.

In `@scripts/extract-panel-surface.py`:
- Around line 2-5: Correct the usage example in the module docstring to use the
actual script name extract-panel-surface.py, matching the filename invoked by
scripts/panel-surface.sh.

---

Nitpick comments:
In `@internal/easypanel/routes_test.go`:
- Around line 130-141: Update the join helper to retain the sorted copy from
sort.Strings and return strings.Join(sorted, ",") instead of manually
concatenating elements; add the strings import required for this
standard-library helper.

In `@internal/easypanel/routes.go`:
- Around line 25-26: Update the comment describing the per-type procedure names
to reference typeOnlyProcedures instead of the stale appOnlyProcedures
identifier, matching the variable declared near the router definitions.

In `@internal/server/getlogs_test.go`:
- Around line 16-36: Add a test alongside
TestGetLogsRejectsDisabledLogAggregation that leaves
easypanel.RouteLogsGetSettings unstubbed, invokes GetLogs with the same request,
and verifies the settings-probe error does not prevent the query route from
being called. Assert the intentional fail-open outcome while keeping the
existing disabled-aggregation test unchanged.

In `@internal/server/paas.go`:
- Around line 379-381: Update the response handling around IncludeEnv in the
relevant server method so clear-text environment values are returned only when
the caller has an explicit authorization scope or permission for environment
access. Reuse the existing auth/interceptor authorization model and preserve
redactEnvValues as the default for unauthorized or absent permission, rather
than relying solely on token membership.

In `@internal/server/panel_fake_test.go`:
- Around line 56-64: The error-response branch in handle must use the status
code encoded in parts[0] rather than always returning http.StatusBadRequest.
Parse parts[0] with strconv, write the parsed status when valid, and preserve
the existing bad-request fallback for malformed or missing status values.

In `@scripts/extract-panel-surface.py`:
- Around line 11-12: Update load to open the file through a with block so its
handle is closed deterministically, and apply the same context-manager pattern
to the json.dump file operation. Add sys.argv length validation before argument
access, reporting usage and exiting cleanly when the required path argument is
missing.
- Line 120: Remove the unused SCHEMA_ALIAS constant declaration, ensuring no
other code or behavior is changed.

In `@scripts/panel-surface.sh`:
- Around line 32-36: Register cleanup immediately after the docker create
command for the container created in the panel-surface extraction flow, so
failures in docker export, tar, or subsequent processing still remove it. Ensure
cleanup runs on script exit while preserving the existing explicit docker rm
behavior without attempting unsafe duplicate removal.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 986bd167-d006-49b6-8909-3c685c1c857b

📥 Commits

Reviewing files that changed from the base of the PR and between 42be694 and 0bb21c8.

⛔ Files ignored due to path filters (1)
  • proto/paas.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (26)
  • .env.example
  • CHANGELOG.md
  • README.md
  • cmd/grpc-server/main.go
  • cmd/services.go
  • cmd/settings.go
  • docs/deployment-guide.md
  • grpc_e2e_test.go
  • internal/easypanel/client.go
  • internal/easypanel/routes.go
  • internal/easypanel/routes_test.go
  • internal/easypanel/testdata/panel-surface-2.32.2.json
  • internal/easypanel/testdata/panel-surface-2.33.0.json
  • internal/easypanel/testdata/panel-surface-2.33.1.json
  • internal/easypanel/version.go
  • internal/easypanel/version_test.go
  • internal/server/env.go
  • internal/server/env_test.go
  • internal/server/getlogs_test.go
  • internal/server/logs.go
  • internal/server/paas.go
  • internal/server/panel_fake_test.go
  • internal/server/status_test.go
  • proto/paas.proto
  • scripts/extract-panel-surface.py
  • scripts/panel-surface.sh

Comment thread cmd/grpc-server/main.go
Comment on lines +41 to +46
// Probe the panel version so route drift shows up at boot instead of on the
// first failing RPC. Never fatal: a slow or briefly unreachable panel should
// not stop the server from serving.
if os.Getenv("EASYPANEL_SKIP_VERSION_CHECK") == "" {
reportPanelVersion(ep, endpoint)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Only skip the version probe when the value is 1.

EASYPANEL_SKIP_VERSION_CHECK=0 currently skips the probe. This conflicts with the documented =1 contract and can hide an unsupported panel version.

Proposed fix
-	if os.Getenv("EASYPANEL_SKIP_VERSION_CHECK") == "" {
+	if os.Getenv("EASYPANEL_SKIP_VERSION_CHECK") != "1" {
 		reportPanelVersion(ep, endpoint)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Probe the panel version so route drift shows up at boot instead of on the
// first failing RPC. Never fatal: a slow or briefly unreachable panel should
// not stop the server from serving.
if os.Getenv("EASYPANEL_SKIP_VERSION_CHECK") == "" {
reportPanelVersion(ep, endpoint)
}
// Probe the panel version so route drift shows up at boot instead of on the
// first failing RPC. Never fatal: a slow or briefly unreachable panel should
// not stop the server from serving.
if os.Getenv("EASYPANEL_SKIP_VERSION_CHECK") != "1" {
reportPanelVersion(ep, endpoint)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/grpc-server/main.go` around lines 41 - 46, Update the
EASYPANEL_SKIP_VERSION_CHECK condition around reportPanelVersion so the probe is
skipped only when the environment value equals "1"; run reportPanelVersion for
unset, "0", and all other values.

Comment thread docs/deployment-guide.md
Comment on lines +14 to +22
// Supported panel range. The route surface this project depends on
// (services.app.*, services.compose.*, domains.*, logs.*, projects.*) is
// byte-identical across 2.32.0 through 2.33.1; MinSupportedVersion is the
// oldest release verified against the pinned route surfaces in testdata, and
// MaxTestedVersion is the newest.
const (
MinSupportedVersion = "2.32.0"
MaxTestedVersion = "2.33.1"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The declared support floor is not backed by a pinned surface. MinSupportedVersion is 2.32.0, but the oldest fixture in internal/easypanel/testdata/ is panel-surface-2.32.2.json. No fixture covers 2.32.0 or 2.32.1, so TestRequiredRoutesExistInEverySupportedPanel never verifies the routes for the lowest supported release. TestSupportWindowMatchesPinnedSurfaces only asserts oldest >= MinSupportedVersion, so the gap passes silently. Choose one resolution and apply it at both sites.

  • internal/easypanel/version.go#L14-L22: either set MinSupportedVersion to 2.32.2 to match the oldest fixture, or add a 2.32.0 fixture and keep the constant. Also correct the comment, which currently states that MinSupportedVersion is "the oldest release verified against the pinned route surfaces in testdata".
  • scripts/panel-surface.sh#L18-L22: update the default VERSIONS list so it starts at the chosen floor, keeping the comment on Line 18 accurate.
📍 Affects 2 files
  • internal/easypanel/version.go#L14-L22 (this comment)
  • scripts/panel-surface.sh#L18-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/easypanel/version.go` around lines 14 - 22, Align the supported
version floor with pinned coverage: in internal/easypanel/version.go lines
14-22, either set MinSupportedVersion to 2.32.2 or add a 2.32.0 fixture while
correcting the surrounding comment; in scripts/panel-surface.sh lines 18-22,
update the default VERSIONS list to start at the same chosen floor and keep its
comment accurate.

if err != nil {
return Version{}, fmt.Errorf("panel version: %w", err)
}
defer resp.Body.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Handle the resp.Body.Close return value.

golangci-lint errcheck reports the unchecked return on this line. Assign it explicitly to keep the linter green.

🔧 Proposed fix
-	defer resp.Body.Close()
+	defer func() { _ = resp.Body.Close() }()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 110-110: Error return value of resp.Body.Close is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/easypanel/version.go` at line 110, Update the deferred response-body
cleanup in the surrounding HTTP request function to explicitly discard the
return value from resp.Body.Close, satisfying errcheck while preserving the
existing cleanup behavior.

Source: Linters/SAST tools

Comment thread internal/server/paas.go
Comment thread README.md Outdated
Comment on lines +2 to +5
"""Extract the Easypanel backend route surface from a bundled backend.js.

Usage: epsurface.py <path-to-backend.js> <out.json>
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the script name in the usage line.

The usage line names epsurface.py. The file is scripts/extract-panel-surface.py, and scripts/panel-surface.sh invokes it under that name.

📝 Proposed fix
-Usage: epsurface.py <path-to-backend.js> <out.json>
+Usage: extract-panel-surface.py <path-to-backend.js> <out.json>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""Extract the Easypanel backend route surface from a bundled backend.js.
Usage: epsurface.py <path-to-backend.js> <out.json>
"""
"""Extract the Easypanel backend route surface from a bundled backend.js.
Usage: extract-panel-surface.py <path-to-backend.js> <out.json>
"""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/extract-panel-surface.py` around lines 2 - 5, Correct the usage
example in the module docstring to use the actual script name
extract-panel-surface.py, matching the filename invoked by
scripts/panel-surface.sh.

- scripts/extract-panel-surface.py resolved a schema alias only when the
  preceding character was a separator, so `}var uo=g.object({...})` in 2.32.0
  looked like an empty input. Match on a non-identifier boundary instead. This
  makes 2.32.0 resolve identically to the newer releases.
- Pin 2.32.0 as well, so both ends of the declared support window are backed by
  a fixture, and require MinSupportedVersion to equal the oldest pinned surface.
  All four pinned releases agree on every route this project uses.
- EASYPANEL_SKIP_VERSION_CHECK is parsed as a boolean, so `=0` no longer skips
  the probe.
- The disabled-log-aggregation precheck invalidates the cached settings, so an
  operator enabling aggregation takes effect on the next GetLogs call instead of
  after the cache TTL. The error message tells callers to retry, so it has to be
  true.
- README: language on the error-output fence, refreshed pinned-version notes.
@igun997
igun997 merged commit ad5fc63 into master Aug 6, 2026
3 checks passed
@igun997
igun997 deleted the feat/panel-version-pinning-and-runtime-status branch August 6, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant