Skip to content

Develop - #5

Open
et-nik wants to merge 11 commits into
masterfrom
develop
Open

Develop#5
et-nik wants to merge 11 commits into
masterfrom
develop

Conversation

@et-nik

@et-nik et-nik commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Enabled gRPC-only daemon enrollment and panel communication, including updated enrollment flow and commands.
    • Added repository URL mirror replacements with priority-based fallback.
    • Added snapshot/delta-based server task scheduling with cancellation, overlap, and catch-up policies, including streamed large outputs.
  • Bug Fixes

    • Improved command argument execution to avoid unintended shell expansion and preserve quoting.
    • File listings now correctly error on missing or non-directory paths.
    • Duplicate upload transfers are now rejected immediately.
  • Documentation

    • Updated configuration guidance for gRPC endpoint/mTLS, enrollment settings, metrics filters, and removed legacy configuration keys.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The daemon is migrated to gRPC-only communication, introduces synchronized server-task scheduling, replaces shell-parsed commands with argument vectors, adds prioritized repository mirrors, simplifies REST-backed services, and updates configuration, enrollment, platform launchers, and tests.

Changes

Daemon runtime migration

Layer / File(s) Summary
Configuration and repository replacement contracts
internal/app/config/*, README.md, config/gameap-daemon.yaml, go.mod
Configuration now requires gRPC/API key settings, supports TLS/insecure transport, adds metrics filters and repository replacement validation, and documents enrollment and removed legacy keys.
Argument-vector command execution
internal/app/domain/commands.go, internal/app/components/*, internal/processmanager/*, pkg/shellquote/*
Commands are built and executed as argument slices across process managers, with platform-specific quoting and Windows service environment generation.
Server-task domain model
internal/app/domain/server_task.go
Server tasks now use option-based construction with versioning, overlap/catchup policies, metadata, enablement, and synchronized updates.
Server-task scheduler and synchronization
internal/app/servers_scheduler/*
The scheduler applies snapshots and deltas, handles version gaps, catchup, overlap, cancellation, queued executions, output streaming, and lifecycle messages.
gRPC runtime and dependency wiring
internal/app/grpc/client.go, internal/app/di/*, internal/app/services/*, internal/app/run.go, internal/app/repositories/*, internal/app/gdaemon_scheduler/*
gRPC message routing, scheduler injection, cache-backed repositories, task status sending, and unconditional gRPC startup replace REST and inbound server wiring.
Remote repository installation candidates
internal/app/game_server_commands/*
Game and mod installation rules generate prioritized mirror candidates and retain the original repository as fallback.
Filesystem and transfer handling
internal/app/grpc/file_handler.go, internal/app/grpc/transfer_handler.go
Missing file-list roots now return errors, and duplicate transfers receive explicit failure responses.
Functional scheduler validation
test/functional/gdtasks/*, test/functional/server_tasks/scheduler_grpc_test.go
Functional harnesses track inserted tasks directly and public scheduler tests validate snapshot execution, deletion, and emitted messages.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague and does not describe the main change in the pull request. Replace it with a concise, specific title that summarizes the primary change, such as the new gRPC-only enrollment and scheduler refactor.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/app/gdaemon_scheduler/task_manager.go (2)

205-221: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing predecessor now retries forever instead of eventually failing.

Previously a predecessor not found in the queue or completion tracker was resolved via the repository, which could return predecessorFail. Now it unconditionally returns predecessorWait and just logs a warning. Since completed is a fixed-capacity tracker (newCompletionTracker(completionTrackerCapacity)), a predecessor whose record is evicted — or a task referencing a stale/invalid RunAfterID — will wait indefinitely: the dependent task can never proceed or fail, staying in the queue and being reprocessed on every scheduler tick.

Consider adding a bounded retry count or max-wait duration in this branch so the task eventually fails with a clear "predecessor not found" reason instead of looping forever.

🔧 Sketch of a bounded-wait fallback
-	logger.Logger(ctx).Warnf(
-		"predecessor task %d not found in queue or completion tracker, will retry",
-		runAfterID,
-	)
-
-	return predecessorWait, ""
+	if task.PredecessorWaitExceeded() { // or an equivalent attempt/duration guard
+		logger.Logger(ctx).Errorf(
+			"predecessor task %d not found in queue or completion tracker, giving up",
+			runAfterID,
+		)
+		return predecessorFail, "predecessor task not found"
+	}
+
+	logger.Logger(ctx).Warnf(
+		"predecessor task %d not found in queue or completion tracker, will retry",
+		runAfterID,
+	)
+
+	return predecessorWait, ""
🤖 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/app/gdaemon_scheduler/task_manager.go` around lines 205 - 221,
Update the missing-predecessor branch in the task manager’s predecessor
evaluation flow to track retries or elapsed wait time and eventually return
predecessorFail with a clear “predecessor not found” reason. Preserve the
current predecessorWait behavior while the configured retry or wait bound has
not been exceeded, and ensure the bound applies to stale or evicted RunAfterID
records.

347-375: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Inconsistent output/fail ordering vs. executeCommand/executeGameCommand.

In executeCommand (Lines 296-297) and executeGameCommand (Lines 339-340), notifyTaskOutput is called before failTask. Here, failTask (Line 368) runs before notifyTaskOutput (Line 372) for the same "final, not successful" case. If failTask signals task completion downstream, the last output chunk may be delivered after consumers have already stopped listening.

🔧 Proposed reorder for consistency
 	if isFinal {
 		manager.commandsInProgress.Delete(task.ID())
+		manager.notifyTaskOutput(task, output, isFinal)
 
 		if cmd.Result() == gameservercommands.SuccessResult {
 			err := task.SetStatus(domain.GDTaskStatusSuccess)
 			if err != nil {
 				return err
 			}
 			manager.notifyTaskStatus(task, "Task completed successfully")
 		} else {
 			manager.failTask(ctx, task)
 		}
+
+		return nil
 	}
 
 	manager.notifyTaskOutput(task, output, isFinal)
 
 	return nil
 }
🤖 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/app/gdaemon_scheduler/task_manager.go` around lines 347 - 375, In
proceedTask, ensure the final output is delivered before failure completion is
signaled: move notifyTaskOutput ahead of the non-success failTask call while
preserving successful status handling and final-task cleanup. Keep the existing
output notification arguments and ensure both successful and failed final
executions retain consistent ordering with executeCommand and
executeGameCommand.
🧹 Nitpick comments (1)
internal/processmanager/simple.go (1)

65-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

GetOutput duplicates execCommand's build+exec pattern.

GetOutput re-implements the same build-args → exec → wrap-error sequence that execCommand (lines 92-111) already centralizes, differing only in using pm.executor instead of pm.detailedExecutor. Consider parameterizing execCommand with the executor to use, or adding a variant, to avoid the two copies diverging over time.

♻️ Proposed consolidation
-func (pm *Simple) GetOutput(
-	ctx context.Context, server *domain.Server, out io.Writer,
-) (domain.Result, error) {
-	args, err := domain.BuildCommandArgs(pm.cfg, server, pm.cfg.Scripts.GetConsole, "")
-	if err != nil {
-		return domain.ErrorResult, errors.WithMessage(err, "failed to build command")
-	}
-
-	result, err := pm.executor.ExecWithWriterArgs(
-		ctx,
-		args,
-		out,
-		pm.executeOptions(server),
-	)
-	if err != nil {
-		return domain.ErrorResult, errors.WithMessage(err, "failed to exec command")
-	}
-
-	return domain.Result(result), nil
-}
+func (pm *Simple) GetOutput(
+	ctx context.Context, server *domain.Server, out io.Writer,
+) (domain.Result, error) {
+	return pm.execCommandWith(ctx, pm.executor, server, pm.cfg.Scripts.GetConsole, "", out)
+}

Then generalize execCommand to accept the executor, e.g. execCommandWith(ctx, executor, server, wrapper, serverCommand, out), and have execCommand call it with pm.detailedExecutor.

🤖 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/processmanager/simple.go` around lines 65 - 84, Consolidate
GetOutput’s duplicated command-building and execution flow with execCommand by
introducing a shared executor-parameterized helper, such as execCommandWith.
Preserve the existing error wrapping and command arguments, have execCommand
delegate using pm.detailedExecutor, and have GetOutput use pm.executor through
the shared path.
🤖 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 `@config/gameap-daemon.yaml`:
- Around line 14-16: Replace the concrete-looking value assigned to api_key in
the sample configuration with an unmistakably fake placeholder such as
your-api-key-here, while preserving the key name and surrounding configuration.

In `@internal/app/domain/server_task.go`:
- Around line 219-241: Protect all ServerTask field reads with s.mutex to match
UpdateFromOptions: update ServerID(), NodeID(), Command(), Server(),
OverlapPolicy(), CatchupPolicy(), Name(), Timezone(), Payload(), Enabled(),
UpdatedAt(), and IsActive() to lock consistently, preferably with defer unlock.
Preserve each getter’s existing return behavior while ensuring no listed field
is read lock-free.

In `@internal/app/grpc/file_handler.go`:
- Around line 275-277: Update the recursive listing logic around the os.Stat
check to reject roots that are not directories by validating info.IsDir(), and
propagate WalkDir errors when the visited path equals dirPath instead of
swallowing root-level failures. Keep valid directory listings unchanged, and add
tests covering a regular-file root and an error at path == dirPath.

In `@internal/processmanager/shawl_windows.go`:
- Around line 403-409: Stop writing the full binPath, which contains the wrapped
game command and potential credentials, to task output. Update the
output/logging in the service-creation flow around binPath and the scArgs
construction to report only safe metadata or a redacted value, while preserving
the existing vector-based sc.exe argument construction unchanged.

In `@README.md`:
- Around line 81-83: Update Config.IsInsecure() to treat cfg.GRPC.Insecure as an
insecure connection condition, ensuring certificate settings are not required
when grpc.insecure is true. Preserve the existing grpc.address and api_host
checks, and keep the README exception aligned with this validation behavior.

---

Outside diff comments:
In `@internal/app/gdaemon_scheduler/task_manager.go`:
- Around line 205-221: Update the missing-predecessor branch in the task
manager’s predecessor evaluation flow to track retries or elapsed wait time and
eventually return predecessorFail with a clear “predecessor not found” reason.
Preserve the current predecessorWait behavior while the configured retry or wait
bound has not been exceeded, and ensure the bound applies to stale or evicted
RunAfterID records.
- Around line 347-375: In proceedTask, ensure the final output is delivered
before failure completion is signaled: move notifyTaskOutput ahead of the
non-success failTask call while preserving successful status handling and
final-task cleanup. Keep the existing output notification arguments and ensure
both successful and failed final executions retain consistent ordering with
executeCommand and executeGameCommand.

---

Nitpick comments:
In `@internal/processmanager/simple.go`:
- Around line 65-84: Consolidate GetOutput’s duplicated command-building and
execution flow with execCommand by introducing a shared executor-parameterized
helper, such as execCommandWith. Preserve the existing error wrapping and
command arguments, have execCommand delegate using pm.detailedExecutor, and have
GetOutput use pm.executor through the shared path.
🪄 Autofix (Beta)

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: d6f23116-f1ad-4615-81e7-88a1f9678e16

📥 Commits

Reviewing files that changed from the base of the PR and between 413659c and 29e5ed8.

⛔ Files ignored due to path filters (2)
  • config/certs_panel/dh2048.pem is excluded by !**/*.pem
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (144)
  • .github/workflows/release.yml
  • README.md
  • config/certs/client.crt
  • config/certs/client.csr
  • config/certs/client.key
  • config/certs_panel/ca.crt
  • config/certs_panel/server.crt
  • config/certs_panel/server.csr
  • config/certs_panel/server.key
  • config/gameap-daemon.yaml
  • go.mod
  • internal/app/components/executor.go
  • internal/app/components/extendable_executor.go
  • internal/app/config/config.go
  • internal/app/config/config_test.go
  • internal/app/config/errors.go
  • internal/app/config/loader.go
  • internal/app/config/loader_test.go
  • internal/app/config/loader_unix_test.go
  • internal/app/config/loader_windows_test.go
  • internal/app/config/nodeConfig.go
  • internal/app/config/repository_replacements.go
  • internal/app/config/repository_replacements_test.go
  • internal/app/config/scripts.go
  • internal/app/config/writer.go
  • internal/app/config/writer_test.go
  • internal/app/contracts/contracts.go
  • internal/app/di/container.go
  • internal/app/di/internal/_config.go
  • internal/app/di/internal/container.go
  • internal/app/di/internal/definitions/container.go
  • internal/app/di/internal/definitions/contracts.go
  • internal/app/di/internal/definitions/grpc.go
  • internal/app/di/internal/definitions/repositories.go
  • internal/app/di/internal/definitions/services.go
  • internal/app/domain/api_request.go
  • internal/app/domain/commands.go
  • internal/app/domain/commands_args_test.go
  • internal/app/domain/errors.go
  • internal/app/domain/gdaemon_task.go
  • internal/app/domain/server_task.go
  • internal/app/enroll_cmd.go
  • internal/app/game_server_commands/install_server.go
  • internal/app/game_server_commands/install_server_test.go
  • internal/app/game_server_commands/repository_replacer.go
  • internal/app/game_server_commands/repository_replacer_test.go
  • internal/app/gdaemon_scheduler/task_manager.go
  • internal/app/grpc/client.go
  • internal/app/grpc/file_handler.go
  • internal/app/grpc/file_handler_test.go
  • internal/app/grpc/transfer_handler.go
  • internal/app/repositories/errors.go
  • internal/app/repositories/gdtask_repository.go
  • internal/app/repositories/server_repository.go
  • internal/app/repositories/server_task_repository.go
  • internal/app/run.go
  • internal/app/server/commands/commands.go
  • internal/app/server/commands/requests.go
  • internal/app/server/commands/response.go
  • internal/app/server/enum.go
  • internal/app/server/files/enum.go
  • internal/app/server/files/files.go
  • internal/app/server/files/os_utils.go
  • internal/app/server/files/os_utils_darwin.go
  • internal/app/server/files/os_utils_linux.go
  • internal/app/server/files/os_utils_windows.go
  • internal/app/server/files/requests.go
  • internal/app/server/files/response.go
  • internal/app/server/messages.go
  • internal/app/server/response/status.go
  • internal/app/server/server.go
  • internal/app/server/server_common/common.go
  • internal/app/server/status/enum.go
  • internal/app/server/status/response.go
  • internal/app/server/status/status.go
  • internal/app/servers_scheduler/cache.go
  • internal/app/servers_scheduler/execution.go
  • internal/app/servers_scheduler/helpers_test.go
  • internal/app/servers_scheduler/output.go
  • internal/app/servers_scheduler/output_test.go
  • internal/app/servers_scheduler/policy.go
  • internal/app/servers_scheduler/policy_test.go
  • internal/app/servers_scheduler/queue.go
  • internal/app/servers_scheduler/queue_test.go
  • internal/app/servers_scheduler/resync.go
  • internal/app/servers_scheduler/scheduler.go
  • internal/app/servers_scheduler/scheduler_cancel_test.go
  • internal/app/servers_scheduler/scheduler_catchup_test.go
  • internal/app/servers_scheduler/scheduler_output_test.go
  • internal/app/servers_scheduler/scheduler_overlap_test.go
  • internal/app/servers_scheduler/scheduler_test.go
  • internal/app/servers_scheduler/scheduler_version_test.go
  • internal/app/servers_scheduler/types.go
  • internal/app/services/api.go
  • internal/app/services/runner.go
  • internal/processmanager/docker.go
  • internal/processmanager/podman.go
  • internal/processmanager/shawl_windows.go
  • internal/processmanager/simple.go
  • internal/processmanager/systemd.go
  • internal/processmanager/systemd_internal_test.go
  • internal/processmanager/systemd_metrics_test.go
  • internal/processmanager/systemd_quoting_test.go
  • internal/processmanager/tmux.go
  • internal/processmanager/winsw_windows.go
  • pkg/limiter/limiter.go
  • pkg/limiter/limiter_test.go
  • pkg/shellquote/unquote.go
  • pkg/shellquote/windows_arg_test.go
  • test/functional/gdtasks/commands/cmdexec_test.go
  • test/functional/gdtasks/commands/suite_test.go
  • test/functional/gdtasks/suite.go
  • test/functional/repositoriestest/fixtures.go
  • test/functional/repositoriestest/gdtaskrepository/fixtures_test.go
  • test/functional/repositoriestest/gdtaskrepository/gdtask_repository_test.go
  • test/functional/repositoriestest/gdtaskrepository/suite_test.go
  • test/functional/repositoriestest/server_task_repository/server_task_repository_test.go
  • test/functional/repositoriestest/server_task_repository/suite_test.go
  • test/functional/repositoriestest/serverrepository/server_repository_test.go
  • test/functional/repositoriestest/serverrepository/suite_test.go
  • test/functional/repositoriestest/suite.go
  • test/functional/server_tasks/scheduler_grpc_test.go
  • test/functional/server_tasks/server_tasks_test.go
  • test/functional/server_tasks/suite_test.go
  • test/functional/servertest/commands/commands_unix_test.go
  • test/functional/servertest/commands/commands_windows_test.go
  • test/functional/servertest/commands/exec_test.go
  • test/functional/servertest/commands/suite_test.go
  • test/functional/servertest/files/chmod_test.go
  • test/functional/servertest/files/file_info_test.go
  • test/functional/servertest/files/list_test.go
  • test/functional/servertest/files/mkdir_test.go
  • test/functional/servertest/files/move_copy_test.go
  • test/functional/servertest/files/remove_test.go
  • test/functional/servertest/files/suite_test.go
  • test/functional/servertest/files/upload_download_test.go
  • test/functional/servertest/files/validation_test.go
  • test/functional/servertest/status/status_test.go
  • test/functional/servertest/status/suite_test.go
  • test/functional/servertest/suite.go
  • test/manual/client/client.go
  • test/mocks/gdtask_repository.go
  • test/mocks/server_task_repository.go
  • test/mocks/task_stats_reader.go
💤 Files with no reviewable changes (79)
  • config/certs_panel/server.key
  • config/certs_panel/ca.crt
  • internal/app/server/files/os_utils_windows.go
  • config/certs_panel/server.csr
  • internal/app/server/enum.go
  • test/functional/servertest/status/status_test.go
  • internal/app/server/files/enum.go
  • internal/app/config/loader.go
  • internal/app/repositories/errors.go
  • internal/app/server/files/os_utils_darwin.go
  • test/functional/repositoriestest/gdtaskrepository/fixtures_test.go
  • config/certs/client.csr
  • internal/app/server/files/os_utils.go
  • test/functional/servertest/commands/commands_windows_test.go
  • config/certs/client.crt
  • internal/app/config/loader_unix_test.go
  • internal/app/server/commands/requests.go
  • test/functional/servertest/status/suite_test.go
  • pkg/limiter/limiter_test.go
  • internal/app/di/internal/definitions/container.go
  • config/certs_panel/server.crt
  • test/functional/servertest/commands/commands_unix_test.go
  • test/functional/servertest/files/chmod_test.go
  • test/functional/repositoriestest/fixtures.go
  • test/functional/servertest/files/validation_test.go
  • test/mocks/gdtask_repository.go
  • test/functional/servertest/commands/suite_test.go
  • internal/app/server/server_common/common.go
  • internal/app/server/files/os_utils_linux.go
  • internal/app/repositories/gdtask_repository.go
  • internal/app/domain/gdaemon_task.go
  • internal/app/servers_scheduler/queue_test.go
  • test/mocks/server_task_repository.go
  • internal/app/server/messages.go
  • internal/app/server/commands/response.go
  • internal/app/server/files/files.go
  • internal/app/domain/errors.go
  • internal/app/config/loader_windows_test.go
  • test/functional/gdtasks/commands/suite_test.go
  • pkg/limiter/limiter.go
  • test/mocks/task_stats_reader.go
  • test/functional/servertest/files/file_info_test.go
  • test/functional/servertest/files/list_test.go
  • test/manual/client/client.go
  • test/functional/servertest/files/mkdir_test.go
  • config/certs/client.key
  • internal/app/server/commands/commands.go
  • test/functional/servertest/files/suite_test.go
  • test/functional/servertest/files/move_copy_test.go
  • test/functional/repositoriestest/gdtaskrepository/suite_test.go
  • internal/app/server/status/enum.go
  • test/functional/repositoriestest/serverrepository/suite_test.go
  • test/functional/repositoriestest/suite.go
  • internal/app/server/status/response.go
  • test/functional/repositoriestest/server_task_repository/suite_test.go
  • internal/app/config/nodeConfig.go
  • test/functional/servertest/files/remove_test.go
  • internal/app/repositories/server_task_repository.go
  • internal/app/services/api.go
  • internal/app/server/status/status.go
  • internal/app/domain/api_request.go
  • test/functional/server_tasks/server_tasks_test.go
  • internal/app/config/loader_test.go
  • internal/app/server/server.go
  • internal/app/servers_scheduler/queue.go
  • test/functional/server_tasks/suite_test.go
  • test/functional/repositoriestest/gdtaskrepository/gdtask_repository_test.go
  • internal/app/config/writer.go
  • test/functional/servertest/files/upload_download_test.go
  • test/functional/repositoriestest/serverrepository/server_repository_test.go
  • test/functional/repositoriestest/server_task_repository/server_task_repository_test.go
  • test/functional/servertest/commands/exec_test.go
  • internal/app/server/files/requests.go
  • internal/app/di/container.go
  • internal/app/server/response/status.go
  • internal/app/server/files/response.go
  • internal/app/enroll_cmd.go
  • test/functional/servertest/suite.go
  • internal/app/di/internal/definitions/services.go

Comment thread config/gameap-daemon.yaml
Comment on lines +219 to +241
func (s *ServerTask) UpdateFromOptions(opts ServerTaskOptions) {
s.mutex.Lock()
defer s.mutex.Unlock()

s.serverID = opts.ServerID
s.nodeID = opts.NodeID
s.version = opts.Version
s.command = opts.Command
if opts.Server != nil {
s.server = opts.Server
}
s.executeDate = opts.ExecuteDate
s.repeat = opts.Repeat
s.repeatPeriod = opts.RepeatPeriod
s.counter = opts.Counter
s.overlapPolicy = opts.OverlapPolicy
s.catchupPolicy = opts.CatchupPolicy
s.name = opts.Name
s.timezone = opts.Timezone
s.payload = opts.Payload
s.enabled = opts.Enabled
s.updatedAt = opts.UpdatedAt
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Data race: UpdateFromOptions mutates fields under the mutex, but sibling getters read them lock-free.

UpdateFromOptions writes serverID, nodeID, command, server, repeat, repeatPeriod, overlapPolicy, catchupPolicy, name, timezone, payload, enabled, and updatedAt while holding s.mutex. However ServerID(), NodeID(), Command(), Server(), OverlapPolicy(), CatchupPolicy(), Name(), Timezone(), Payload(), Enabled(), UpdatedAt() (Lines 106-112, 158-184) and IsActive() (Line 212, reading s.enabled) read the same fields without the lock.

The scheduler holds shared *ServerTask pointers in the cache: tick() (ticker goroutine) reads via these getters while ApplySnapshot/ApplyDelta (gRPC handler goroutine) call UpdateFromOptions on the same instance. Concurrent unsynchronized read/write is a data race (go test -race will flag it) with undefined behavior. Lock the read-side getters (and IsActive) consistently, or snapshot the fields under a single lock.

🤖 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/app/domain/server_task.go` around lines 219 - 241, Protect all
ServerTask field reads with s.mutex to match UpdateFromOptions: update
ServerID(), NodeID(), Command(), Server(), OverlapPolicy(), CatchupPolicy(),
Name(), Timezone(), Payload(), Enabled(), UpdatedAt(), and IsActive() to lock
consistently, preferably with defer unlock. Preserve each getter’s existing
return behavior while ensuring no listed field is read lock-free.

Comment thread internal/app/grpc/file_handler.go Outdated
Comment thread internal/processmanager/shawl_windows.go
Comment thread README.md
# Conflicts:
#	README.md
#	internal/app/grpc/file_handler.go
#	internal/app/server/files/files.go
#	internal/app/server/files/response.go
#	internal/app/server/server.go
#	internal/app/services/runner.go
#	internal/processmanager/shawl_windows.go
#	test/functional/server_tasks/suite_test.go
#	test/functional/servertest/files/chmod_test.go
#	test/functional/servertest/files/file_info_test.go
#	test/functional/servertest/files/list_test.go
#	test/functional/servertest/files/mkdir_test.go
#	test/functional/servertest/files/move_copy_test.go
#	test/functional/servertest/files/remove_test.go
#	test/functional/servertest/files/suite_test.go
#	test/functional/servertest/files/upload_download_test.go
#	test/functional/servertest/suite.go
@coveralls

coveralls commented Jul 21, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29874148788

Coverage decreased (-1.3%) to 36.195%

Details

  • Coverage decreased (-1.3%) from the base build.
  • Patch coverage: 327 uncovered changes across 25 files (947 of 1274 lines covered, 74.33%).
  • 142 coverage regressions across 14 files.

Uncovered Changes

Top 10 Files by Coverage Impact Changed Covered %
internal/app/servers_scheduler/scheduler.go 256 199 77.73%
internal/app/grpc/client.go 39 0 0.0%
internal/app/run.go 26 0 0.0%
internal/app/servers_scheduler/policy.go 96 71 73.96%
internal/app/components/executor.go 31 11 35.48%
internal/app/config/scripts.go 19 0 0.0%
internal/processmanager/tmux.go 18 0 0.0%
internal/app/servers_scheduler/execution.go 106 89 83.96%
internal/app/components/extendable_executor.go 26 11 42.31%
internal/app/services/runner.go 14 0 0.0%
Total (32 files) 1274 947 74.33%

Coverage Regressions

142 previously-covered lines in 14 files lost coverage.

Top 10 Files by Coverage Loss Lines Losing Coverage Coverage
internal/app/di/internal/container.go 33 0.0%
internal/app/domain/game.go 26 5.45%
internal/app/domain/server.go 21 55.28%
internal/app/components/executor.go 20 49.57%
internal/app/di/container.go 19 0.0%
internal/app/repositories/server_repository.go 7 0.0%
internal/app/domain/gdaemon_task.go 6 84.52%
internal/app/di/internal/definitions/services.go 2 0.0%
internal/app/gdaemon_scheduler/task_manager.go 2 75.96%
internal/app/services/runner.go 2 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 11615
Covered Lines: 4204
Line Coverage: 36.19%
Coverage Strength: 12644.43 hits per line

💛 - Coveralls

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/processmanager/shawl_windows.go (1)

426-430: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass sc.exe options and values as separate arguments.

sc.exe create requires the equals sign to stay with the option name and the value to follow as a separate argument. These calls currently pass start=auto, obj=..., password=..., and binPath=... as single args, which can cause service creation to fail or misparse.

Also applies to internal/processmanager/shawl_windows.go:454-459.

🤖 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/processmanager/shawl_windows.go` around lines 426 - 430, Update the
scArgs construction in the service-creation flow and the corresponding block
around the second sc.exe invocation so each sc.exe option and value are separate
arguments: keep the equals sign with option names such as start=, obj=,
password=, and binPath=, followed by their values as distinct elements. Preserve
the existing option order and values while applying this consistently to both
command argument lists.

Source: MCP tools

🤖 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 `@internal/processmanager/shawl_windows.go`:
- Around line 466-470: The Debug calls in the service configuration logging
block expose complete command lines, credentials, and unescaped newlines. Remove
the logger.Debug calls for serviceConfig and binPath, or replace them with
non-sensitive redacted metadata that cannot include command contents or raw
newlines.

---

Outside diff comments:
In `@internal/processmanager/shawl_windows.go`:
- Around line 426-430: Update the scArgs construction in the service-creation
flow and the corresponding block around the second sc.exe invocation so each
sc.exe option and value are separate arguments: keep the equals sign with option
names such as start=, obj=, password=, and binPath=, followed by their values as
distinct elements. Preserve the existing option order and values while applying
this consistently to both command argument lists.
🪄 Autofix (Beta)

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: 9bf50c95-60f0-40bb-b129-67adc254adb0

📥 Commits

Reviewing files that changed from the base of the PR and between 6ba53a1 and 9517bc0.

📒 Files selected for processing (13)
  • config/gameap-daemon.yaml
  • internal/app/config/config.go
  • internal/app/config/config_test.go
  • internal/app/domain/server_task.go
  • internal/app/domain/server_task_test.go
  • internal/app/gdaemon_scheduler/const.go
  • internal/app/gdaemon_scheduler/task_manager.go
  • internal/app/gdaemon_scheduler/tasks_manager_test.go
  • internal/app/grpc/connection.go
  • internal/app/grpc/file_handler.go
  • internal/app/grpc/file_handler_test.go
  • internal/processmanager/shawl_windows.go
  • internal/processmanager/simple.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • internal/processmanager/simple.go
  • internal/app/grpc/file_handler.go
  • config/gameap-daemon.yaml
  • internal/app/domain/server_task.go
  • internal/app/gdaemon_scheduler/task_manager.go
  • internal/app/config/config.go

Comment on lines +466 to +470
// The service config and binPath embed the whole game command line, which may
// carry credentials passed as arguments. Task output is streamed to the panel,
// so both stay in the local daemon log only.
logger.Debug(ctx, "Service configuration: "+serviceConfig)
logger.Debug(ctx, "Service binPath: "+binPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not log the full command line or service configuration.

The previous task-output leak was removed, but logger.Debug now writes the same sensitive values to daemon logs. binPath and serviceConfig contain the complete game command and may include credentials; raw embedded newlines also permit log-forging output. Remove these logs or emit only redacted metadata.

Proposed fix
-	// The service config and binPath embed the whole game command line, which may
-	// carry credentials passed as arguments. Task output is streamed to the panel,
-	// so both stay in the local daemon log only.
-	logger.Debug(ctx, "Service configuration: "+serviceConfig)
-	logger.Debug(ctx, "Service binPath: "+binPath)
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 468-468: A log/format call (log.Print/Printf/Println, the Fatal/Panic variants, fmt.Sprintf, or a structured logger's Info/Warn/Error/Debug method) is given a message built by concatenating a string literal with a non-literal value such as request data. Unsanitized, attacker-controlled input written to logs enables log forging / CRLF injection: an attacker can inject newlines to spoof log entries or break log parsers. Do not concatenate raw input into the log message; pass it as a separate structured field/argument (e.g. 'log.Printf("user: %s", user)' or 'logger.Info("login", "user", user)') and strip or escape newline characters first.
Context: logger.Debug(ctx, "Service configuration: "+serviceConfig)
Note: [CWE-117] Improper Output Neutralization for Logs.

(log-injection-request-data-concat-go)


[warning] 469-469: A log/format call (log.Print/Printf/Println, the Fatal/Panic variants, fmt.Sprintf, or a structured logger's Info/Warn/Error/Debug method) is given a message built by concatenating a string literal with a non-literal value such as request data. Unsanitized, attacker-controlled input written to logs enables log forging / CRLF injection: an attacker can inject newlines to spoof log entries or break log parsers. Do not concatenate raw input into the log message; pass it as a separate structured field/argument (e.g. 'log.Printf("user: %s", user)' or 'logger.Info("login", "user", user)') and strip or escape newline characters first.
Context: logger.Debug(ctx, "Service binPath: "+binPath)
Note: [CWE-117] Improper Output Neutralization for Logs.

(log-injection-request-data-concat-go)

🤖 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/processmanager/shawl_windows.go` around lines 466 - 470, The Debug
calls in the service configuration logging block expose complete command lines,
credentials, and unescaped newlines. Remove the logger.Debug calls for
serviceConfig and binPath, or replace them with non-sensitive redacted metadata
that cannot include command contents or raw newlines.

Source: Linters/SAST tools

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.

2 participants