1# Sigil appliance configuration v123`sigil appliance config` is the local, machine-readable settings interface for4an unprivileged appliance controller such as the Decky plugin. It exposes a5small typed projection instead of accepting arbitrary TOML edits.67Every command requires the owner-only host configuration and explicit JSON8output:910```bash11sigil appliance config show --config ~/.config/sigil-spark/host.toml --json12sigil appliance config validate --config ~/.config/sigil-spark/host.toml --json <request.json13sigil appliance config set --config ~/.config/sigil-spark/host.toml --json <request.json14sigil appliance config commit --config ~/.config/sigil-spark/host.toml \15 --transaction <id> --expected-instance <id> --json16sigil appliance config rollback --config ~/.config/sigil-spark/host.toml \17 --transaction <id> --json18```1920The configuration file must be owned by the effective user and not writable by21group or other users. Its immediate directory must also be owned by that user,22must retain owner write and search permission, must not be a symlink, and must23not be writable by group or other users. A mode-`0755` configuration directory24is supported; mode `0700` remains the recommended appliance layout because it25also keeps filenames private. The daemon, `config check`, and every managed26write enforce this same integrity boundary before doing work.2728`set` and `commit` use the service's XDG runtime root to share its global29lifecycle lock and to bind baseline and candidate evidence to one exact runtime30directory. When `XDG_RUNTIME_DIR` is unavailable (for example, from a recovery31shell), pass the same owner-only mode-`0700` root to both commands with32`--runtime-dir /run/user/<uid>`; Sigil appends `sigil-spark`. An explicit root33takes precedence over the environment and must be absolute, owned by the34effective user, and not a symlink. Without either source, `set` and `commit`35fail closed. `rollback` needs no runtime evidence and can use only the durable36state lock when XDG runtime state is unavailable.3738The transaction journal binds `set` to the canonical runtime child with a39non-secret SHA-256 path identifier. `commit` must resolve to that same child40before transaction recovery or mutation, so copying otherwise-valid stopped41status evidence into a different runtime namespace cannot authorize commit.42Legacy journals without this identifier remain rollback-capable but cannot be43committed. Do not downgrade Sigil while a configuration transaction is pending;44older binaries may not understand journals written by this version.4546`show` returns the exact-byte SHA-256 revision, editable settings, and a pending47transaction summary. The editable v1 settings are:4849- Native dimensions or an explicit even width and height.50- Frame rate.51- CBR bitrate or CQP quantizer for a configured Gamescope PipeWire source.5253Source selection, codec, capture backend, executable and device paths,54PipeWire selectors, audio/input configuration, identity, and state paths are55not editable through this contract. Operator comments and all non-managed TOML56fields survive an update.5758Requests are strict JSON and contain an optimistic-concurrency revision:5960```json61{62 "schema_version": 1,63 "expected_revision": "sha256:<64 lowercase hexadecimal digits>",64 "settings": {65 "resolution": { "mode": "native" },66 "framerate": 60,67 "rate_control": { "mode": "cbr", "bitrate_kbps": 12000 }68 }69}70```7172For a synthetic source, `rate_control` must be `null`. A fixed resolution uses73`{"mode":"fixed","width":1280,"height":800}`; CQP uses74`{"mode":"cqp","quantizer":24}`. `validate` performs no writes. A no-op75`set` returns `changed: false`, no transaction ID, and does not require a76restart.7778## Controller sequence7980Only a stopped Sigil can be changed. The controller performs:81821. Stop `sigil-host.service` and wait until it is inactive.832. Call `set`. Sigil atomically installs the candidate and returns a transaction84 ID plus candidate revision. If needed, pass the service's runtime root with85 `--runtime-dir` and retain that exact value for `commit`.863. Start the service and wait for appliance status to report a new instance in87 `ready` state with the candidate revision.884. Stop the service cleanly and retain that exact instance ID.895. Call `commit` with the transaction and instance IDs. If startup or shutdown90 fails, stop the service, call `rollback`, and restart the prior config.9192Commit succeeds only when the bounded runtime document is fresh, belongs to the93current host, reports the exact candidate revision and a daemon instance newer94than the one present at `set`, reached `ready` at least once, stopped cleanly,95and contains no runtime error. A running daemon holds the same lifecycle locks,96so set, commit, rollback, enrollment reset, and service execution cannot race.97Configured service startup takes those locks before its definitive config reload98and transaction recovery. An interrupted prepared install is therefore99completed before capture or networking starts; startup can never launch bytes100that were read before a concurrent `set`.101102## Durability and recovery103104Config files and requests are bounded to 64 KiB and 16 KiB respectively.105Revisions hash the exact bytes read from the same opened file descriptor used106for parsing. Writes and recovery artifacts are owner-only, no-follow, atomic,107and directory-synced.108109The journal records `prepared`, `pending_validation`, `committing`, or110`rolling_back`. Recovery deterministically finishes an interrupted candidate111install, commit cleanup, or byte-exact rollback. Live bytes that match neither112the journaled base nor candidate fail closed instead of being overwritten.113Only one transaction may be pending.114115Management failures write one JSON object to stderr with `schema_version: 1`116and one closed error code: `unsupported_schema`, `invalid_request`,117`validation_failed`, `revision_conflict`, `lifecycle_busy`,118`transaction_busy`, `transaction_pending`, `transaction_not_found`,119`transaction_conflict`, `health_not_proven`, or `unsafe_storage`. They never120include paths, configuration contents, or free-form internal errors.