-
Notifications
You must be signed in to change notification settings - Fork 75
Description
Introduce per-cave settings to allow users to configure launch behavior on a per-game basis, overriding global preferences. Settings are stored in butler's database alongside the cave they belong to (this is tied to their profile?). This could include things like game specific launch arguments, sandbox settings overrides, wine config.
References:
- Per-game custom controller configuration.Β itch#786
- Feature: per-game wine prefixΒ itch#3085 (need migration path for new vs old wine prefix)
- Enable sandbox by default for gamedevs, add per-game 'sandbox exception' systemΒ itch#751
Storage
Approach: A Settings column (type TEXT, stores JSON) on the existing caves table.
This follows the same pattern as the existing Verdict column on caves, which stores a JSON-serialized Go struct as a text blob. Hades (the ORM) auto-migrates new columns β adding a text column with an empty string default requires no explicit migration. An empty string deserializes to a zero-value CaveSettings{} (all fields nil/empty), meaning existing caves behave as if they have no overrides.
Settings are deleted automatically when a cave is uninstalled since they live on the cave row itself.
Types
CaveSettings
Flat per-cave configuration. All fields are optional β omitted fields mean "use global default." Sandbox-related fields are prefixed with sandbox since they only apply when the sandbox is active.
type CaveSettings struct {
// Overrides the global sandbox enabled/disabled preference.
// nil = inherit global, true = force on, false = force off
Sandbox *bool `json:"sandbox,omitempty"`
// Override sandbox runner type (bubblewrap, firejail, flatpak, fuji, auto).
SandboxType *SandboxType `json:"sandboxType,omitempty"`
// Override network restriction within the sandbox.
SandboxNoNetwork *bool `json:"sandboxNoNetwork,omitempty"`
// Override allowed environment variables within the sandbox.
SandboxAllowEnv *[]string `json:"sandboxAllowEnv,omitempty"`
// Additional command-line arguments appended after manifest args.
ExtraArgs []string `json:"extraArgs,omitempty"`
}Override Semantics
Single-level override: per-cave value wins if set, otherwise global preference applies. Each field is independent.
| Per-cave field | Global preference | Effective value |
|---|---|---|
undefined |
any | global value |
| set | any | per-cave value |
ExtraArgs: Always appended after manifest action args. Not an override β additive only.
IPC Methods
Caves.GetSettings
Retrieve per-cave settings for a given cave.
β Request: Caves.GetSettings
{ caveId: string }
β Response:
{ settings: CaveSettings }
Returns a CaveSettings with all nil/empty fields if no settings have been configured.
Caves.SetSettings
Store per-cave settings. Full replacement β the entire CaveSettings object is written, not merged.
β Request: Caves.SetSettings
{ caveId: string, settings: CaveSettings }
β Response:
{}
To clear all overrides, pass an empty CaveSettings{}. To update a single field, the client should GetSettings, modify, then SetSettings.
Launch (modified)
One new optional field on the existing LaunchParams:
β Request: Launch
{
caveId: string,
prereqsDir: string,
sandbox?: boolean,
sandboxOptions?: SandboxOptions,
extraArgs?: string[], // NEW: appended after manifest args
...existing fields...
}
This keeps the client in control of the merge β the itch app reads cave settings, merges with global preferences, and sends the resolved values to Launch. Butler doesn't need to know about global preferences.
Schema Migration
None required. The ORM (hades) auto-migrates by adding the new settings TEXT column with an empty string default. Empty string deserializes to zero-value CaveSettings{}. This is the same mechanism used when the verdict column was added to caves.