Skip to content

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 18 Jul 23:28
Immutable release. Only release title and notes can be modified.
6212266

Minor Changes

  • #53 2363908 Thanks @haydenbleasel! - Add Left 4 Dead 2 as a supported game. Uses the left4devops/l4d2 image with a docker-compose inline server.cfg for cvars that need to survive map loads (z_difficulty, sv_allow_lobby_connect_only). Join passwords are disabled — L4D2's coop lobby flow drops clients after sv_password verification on direct connect, so servers stay private via unlisted IP/port instead.

Patch Changes

  • #67 f1e0609 Thanks @haydenbleasel! - Return 400 instead of an unhandled 500 when the heartbeat, events, or command-ack agent routes receive a signed request with a malformed JSON body. JSON.parse sat outside the routes' error handling, unlike the other agent endpoints.

  • #67 f1e0609 Thanks @haydenbleasel! - Block symlink path traversal in agent file operations. resolveInRoot only normalized the path string, so a symlink planted inside the bind-mounted data root by a compromised game container could redirect FILES_INSTALL_FROM_URL/FILES_DELETE writes outside the root (the agent runs as root on the host). Paths are now resolved through realpath on their deepest existing ancestor and re-verified against the real data root before any read, write, or delete.

  • #67 f1e0609 Thanks @haydenbleasel! - Always validate game settings when creating a server. A request that omitted the settings field skipped validation entirely, bypassing required fields like Don't Starve Together's cluster token and provisioning a paid VM that could never start.

  • #67 f1e0609 Thanks @haydenbleasel! - Prevent double-delivery of agent commands. claimPendingCommands did a find-then-update, so two overlapping long-polls (e.g. an agent retry racing a still-running handler) could both read the same pending rows and execute a command — including destructive ones like FILES_DELETE — twice. Each row is now claimed with a conditional update on status: "pending", so exactly one caller wins it.

  • #67 f1e0609 Thanks @haydenbleasel! - Write the agent's state file atomically. saveState wrote a temp file but then copied it into place with a truncate-and-write, so a crash mid-save could leave a corrupt state.json that permanently bricked the agent on next boot. The temp file is now swapped in with rename.

  • #67 f1e0609 Thanks @haydenbleasel! - Fetch all pages when listing a server's backups and snapshots. listImagesForServer requested a single page of 50 images for the whole account and filtered client-side, so servers on accounts with more than 50 images silently lost backups from the UI. Backups are now filtered server-side via Hetzner's bound_to parameter and both listings follow meta.pagination.next_page to exhaustion.

  • #67 f1e0609 Thanks @haydenbleasel! - Make snapshot builder VM creation idempotent. stepCreateBuilderVm now returns the already-persisted providerBuilderId on retry instead of creating another VM, and the builder name is derived deterministically from the build id so a retried create whose first response was lost is rejected by the provider's unique-name constraint rather than silently billing a second orphaned VM.

  • #67 f1e0609 Thanks @haydenbleasel! - Harden docker-compose value escaping. escapeComposeValue only escaped double quotes, but values are emitted inside YAML double-quoted scalars where \ is the escape character — a backslash in a server name or game setting (e.g. world name, MOTD) produced invalid YAML or terminated the scalar early, and embedded newlines could inject additional YAML lines into the root-run compose file. Backslashes are now escaped before quotes and control characters are stripped.

  • #67 f1e0609 Thanks @haydenbleasel! - Stop docker compose invocations from hanging the agent. The subprocess was spawned with piped stdout/stderr that were never read, so any compose command producing more than the OS pipe buffer (~64KB, easily hit by image-pull progress) blocked forever with no timeout. Both pipes are now drained while awaiting exit, and compose failures include the last stderr line for easier debugging.

  • #67 f1e0609 Thanks @haydenbleasel! - Make agent enrollment atomic. The bootstrap token was burned in a transaction but the agent row was created after it committed, so a transient create failure left the token burned with no agent — the VM's retries then hit "Token already used" forever and the server had to be re-provisioned. The burn is now a conditional update inside the same transaction as the create, which also closes the race where two concurrent requests could both pass the burned-token check.

  • #67 f1e0609 Thanks @haydenbleasel! - Deduplicate retried agent event batches. When the agent re-sent a batch after a lost response, the server assigned fresh ids and seqs, duplicating activity timeline entries and log lines. Agent-sourced rows now use deterministic ids derived from the protocol's clientEventId (activity) and agentSeq (logs), scoped by agent id, so replayed inserts are dropped.

  • #53 2363908 Thanks @haydenbleasel! - Regenerate the Hetzner OpenAPI types. Picks up @deprecated markers on the singular get_<resource>_action endpoints (per the 2026-04-30 changelog), the Load Balancer service/target type split, the primary/secondary Zone schema split, and per-location pricing breakdowns. None of the endpoints we call were affected.

  • #67 f1e0609 Thanks @haydenbleasel! - Close the console log stream on unmount. LogsStream's effect cleanup only set a flag and never closed the live EventSource or cleared the pending reconnect timer, so navigating away from the Console tab left the SSE connection (and the server's 500ms log poll loop) running for up to the route's 300s maxDuration, with stale listeners still updating state.

  • #67 f1e0609 Thanks @haydenbleasel! - Size the Minecraft JVM heap from the actual machine instead of a hardcoded 6G. Rescaling a Minecraft server previously had no effect on the game (and on machines smaller than the default the fixed heap could exceed total RAM). The compose builder now receives the provider-reported machine memory and gives the JVM ~75% of it; the heap is applied whenever a new compose is pushed (install and settings updates).

  • #67 f1e0609 Thanks @haydenbleasel! - Stop reporting transient provisioning errors as "Owner has not configured provider credentials". The catch in stepCreateProviderServer swallowed every error from the provider lookup (including DB blips) into a permanent FatalError; it now only converts MissingProviderCredentialsError and lets everything else retry.

  • 5c50664 Thanks @haydenbleasel! - Refactor the Hetzner integration behind a provider-agnostic Provider interface so a future AWS or GCP backend can be added by writing a new implementation rather than rewriting call sites. lib/hetzner/ is replaced by lib/providers/ (interface + types + per-user factory) with lib/providers/hetzner/ as the only implementation today; all 23 call sites (workflow steps, route handlers, server actions, page loaders) now consume the interface and pass string IDs end-to-end (no more Number() round-trips). Workflow steps are renamed to drop the Hetzner prefix (stepCreateHetznerServerstepCreateProviderServer, etc.). The Prisma schema renames hetznerToken / hetznerImageId / hetznerServerId / hetznerBuilderId to provider* and adds a provider String @default("hetzner") column to User, Server, UserSnapshot, and SnapshotBuild; the migration uses ALTER TABLE … RENAME COLUMN so existing data is preserved. UI copy and the /api/account/hetzner route path remain Hetzner-named — those are deferred until a second provider lands.

  • #67 f1e0609 Thanks @haydenbleasel! - Fix races between provisioning and deletion. Deleting a server while its VM create was in flight could leak the Hetzner VM forever (teardown saw no providerServerId, and the provision step's retry path skipped cleanup) and could mark the deleted server as "failed". Now the cancelled paths of stepCreateProviderServer delete the VM themselves, the post-create state update no longer clobbers teardown's "deleted" phase, stepMarkFailed skips deleted servers, and waitForServerRunning checks for cancellation before treating a vanished VM as a fatal provisioning error.

  • #67 f1e0609 Thanks @haydenbleasel! - Don't mark a successful snapshot build as failed when post-save cleanup hiccups. stepDeleteBuilder runs after the snapshot id is saved and the build is "ready", so provider errors while deleting the builder VM are now swallowed (matching stepDeletePreviousSnapshot) instead of landing in the catch handler and flipping the build to "failed".

  • #53 2363908 Thanks @haydenbleasel! - Show the docker image string on the new-server summary card and the Details tab so it's clear which image will be (or is) running.

  • #67 f1e0609 Thanks @haydenbleasel! - Stop leaking snapshot images on failed builds. The image id is now persisted to the build row immediately after create_image (making the step retry-safe), and the build failure handler deletes any image that never reached "ready" instead of leaving a per-GB-billed orphan snapshot behind.

  • #67 f1e0609 Thanks @haydenbleasel! - Don't permanently block snapshot builds when the workflow fails to start. The build row was committed as "pending" before start(buildSnapshot, …); if the start threw, the row stayed non-terminal and the "already running" guard rejected every future build for that user. The row is now marked "failed" when the workflow can't be started.

  • #67 f1e0609 Thanks @haydenbleasel! - Validate the cursor query parameter on the activity and log SSE streams. A non-numeric cursor became NaN, flowed into Prisma as seq: { gt: NaN }, and errored the stream immediately; it now falls back to 0.

  • #67 f1e0609 Thanks @haydenbleasel! - Ack unimplemented agent commands as failed instead of succeeded. UPLOAD_BACKUP and FETCH_LOGS exist in the protocol but have no agent implementation; they previously fell through the switch and were acked as "succeeded", which would falsely report a backup or log fetch as done if one were ever enqueued.