diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index bef450df..a9f8ae57 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -574,10 +574,12 @@ pub enum Command { #[arg(long)] allow_forward_insecure: bool, - /// Permit durable Wasmi extensions and restore desired definitions - /// (or set BLIT_ALLOW_EXT_PERSIST=1) + /// Refuse durable Wasmi extensions and do not restore desired + /// definitions (or set BLIT_ALLOW_EXT_PERSIST=0). Transient + /// extensions still run. This is the recovery path for a persistent + /// definition that breaks the server it starts in. #[arg(long)] - allow_persistent_extensions: bool, + no_persistent_extensions: bool, #[command(flatten)] deployment: ServerDeploymentOpts, diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index b6834cc2..b35318a8 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1075,7 +1075,7 @@ async fn async_main() { microphone_codecs, allow_forward, allow_forward_insecure, - allow_persistent_extensions, + no_persistent_extensions, deployment, verbose, no_processes, @@ -1200,8 +1200,8 @@ async fn async_main() { .unwrap_or(false), allow_forward, allow_forward_insecure, - allow_persistent_extensions: allow_persistent_extensions - || std::env::var("BLIT_ALLOW_EXT_PERSIST").is_ok_and(|value| value == "1"), + allow_persistent_extensions: !no_persistent_extensions + && !std::env::var("BLIT_ALLOW_EXT_PERSIST").is_ok_and(|value| value == "0"), }; blit_server::run(config).await; } diff --git a/crates/server/src/extension/mod.rs b/crates/server/src/extension/mod.rs index a04a58fa..ef5b79b7 100644 --- a/crates/server/src/extension/mod.rs +++ b/crates/server/src/extension/mod.rs @@ -469,7 +469,7 @@ impl ExtensionService { if !persist_allowed && definition.desired() && definition.enabled() { definition.phase = EXT_PHASE_BLOCKED; definition.detail = - "persistent extensions require operator permission".into(); + "persistent extensions are disabled on this server".into(); } if definition.phase != EXT_PHASE_BACKOFF { definition.next_start_unix_ms = 0; @@ -1350,7 +1350,7 @@ impl ExtensionService { nonce, EXT_STATUS_PERMISSION, hash, - "persistent extensions require --allow-persistent-extensions", + "persistent extensions are disabled on this server", ), ) .await; @@ -2209,7 +2209,7 @@ impl ExtensionService { nonce, EXT_STATUS_PERMISSION, None, - "persistent extensions require operator permission", + "persistent extensions are disabled on this server", )); } else if current .owner_endpoint diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 6d02e920..8e5b7b9f 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -445,7 +445,9 @@ pub struct Config { pub allow_forward: Vec, pub allow_forward_insecure: bool, /// Permit durable extension create/update/control and startup restore. - /// Transient extensions remain available when this gate is false. + /// True by default; `--no-persistent-extensions` turns it off, which is + /// how a bad definition gets repaired. Transient extensions remain + /// available when this gate is false. pub allow_persistent_extensions: bool, } diff --git a/docs/design/env.md b/docs/design/env.md index 6aa845c7..9f504aa2 100644 --- a/docs/design/env.md +++ b/docs/design/env.md @@ -81,10 +81,9 @@ Two things bound it, neither of which should be mistaken for a sandbox: The asymmetry worth understanding is _who_ is reading. A PTY child prints the environment because a person typed a command; an extension reads it unattended, -at session start, from code the operator installed once. Persistent extensions -already require `--allow-persistent-extensions`, so the operator has opted in to -running that code — but they opted in to _running_ it, not necessarily to handing -it their credentials. An operator who wants the session-shaped values without the +at session start, from code the operator installed once. Installing a persistent +extension is opting in to _running_ that code across restarts, not necessarily +to handing it their credentials. An operator who wants the session-shaped values without the secrets should set `BLIT_ENV=0` and rely on [processes.md](processes.md)'s `PROCESS_SPAWN_SESSION_ENV`, which applies the session environment to a child **server-side** without ever naming it on the diff --git a/docs/design/extensions.md b/docs/design/extensions.md index 0a9d0056..00c16265 100644 --- a/docs/design/extensions.md +++ b/docs/design/extensions.md @@ -2385,10 +2385,16 @@ directories must be owner-only: mode `0700` directories and `0600` files on Unix, and user-only ACLs where available. This RFC deliberately adds no extension-specific capability system. -Durable extension execution is separately opt-in. The server must start with -`--allow-persistent-extensions` (or `BLIT_ALLOW_EXT_PERSIST=1`) to create, +Durable extension execution is permitted by default, because persistence adds +durability to authority an endpoint already has rather than adding privilege, +and because an extension without it is half-installed: it runs, but its +`@name` command namespace never exists and nothing survives a restart. A gate +whose off position silently breaks the feature is a gate operators discover by +being confused, so the default is on and the switch is a deliberate off: +`--no-persistent-extensions` (or `BLIT_ALLOW_EXT_PERSIST=0`) refuses to create, update, enable, restart, or automatically restore a persistent extension. -Without that switch, transient extensions still work and feature bit 11 remains + +With that switch, transient extensions still work and feature bit 11 remains advertised. Stored definitions are loaded and pin their objects, but no attempt is restored; `LIST`, `STATUS`, `CANCEL`, `DISABLE`, and `REMOVE` remain available so the catalog can be repaired. `ATTACH` and `UNFOLLOW` also remain @@ -2396,20 +2402,20 @@ available because they change only the caller's observation cursor. Operations w start persistent code return `PERMISSION`, and both lifecycle bits are left unchanged rather than silently disabled. An enabled, desired-running definition which is held only by this gate reports `BLOCKED`, zero task ID, and a detail -which names the missing operator opt-in; a disabled or stopped definition -continues to report `STOPPED`. +which says persistence is disabled on this server; a disabled or stopped +definition continues to report `STOPPED`. -This gate is also the recovery path for a bad persistent definition. For -example, an extension with `--restart always` which sends `C2S_QUIT` could +That switch is the recovery path for a bad persistent definition. For +example, an extension with `--restart always` which sends `C2S_QUIT` would otherwise stop each new server process immediately: ```bash -# Start without --allow-persistent-extensions. -blit server +# Start with persistence off, so the bad definition is not restored. +blit server --no-persistent-extensions blit ext disable BAD_NAME blit ext status BAD_NAME # wait for quiescent STOPPED/BLOCKED blit ext remove BAD_NAME # optional; only after quiescence -# Restart with --allow-persistent-extensions after repair. +# Restart normally after repair. ``` Deployments can hard-disable the new families at process startup, following @@ -2477,9 +2483,8 @@ rather than the CLI process status. `--restart` accepts `never` (the default), `on-failure`, or `always`. `--persist` implies `--detach` and stores an enabled, desired-running definition for future blit server processes. It receives -`PERMISSION` unless the selected -server was started with `--allow-persistent-extensions`; the CLI reports that -operator action directly. `--json` emits supervisor, attempt, and event records +`PERMISSION` if the selected server was started with +`--no-persistent-extensions`; the CLI reports that operator decision directly. `--json` emits supervisor, attempt, and event records as NDJSON envelopes. The CLI records `last_running_attempt` from the correlated creation reply diff --git a/docs/systemd-user-units.md b/docs/systemd-user-units.md index be02eaad..312e4414 100644 --- a/docs/systemd-user-units.md +++ b/docs/systemd-user-units.md @@ -147,9 +147,9 @@ blit @session enable blit-user-manager blit @session status blit-user-manager ``` -`--persist` requires the server to have been started with -`--allow-persistent-extensions`; that is also what makes the intent survive a -restart. `enable` is durable — it is stored under `ext/session/app/` in the +`--persist` requires a server that permits persistent extensions, which is the +default (`--no-persistent-extensions` turns it off); that is also what makes the +intent survive a restart. `enable` is durable — it is stored under `ext/session/app/` in the server's KV store and replayed at startup, so the manager comes up with the session from then on. diff --git a/e2e/start-servers.sh b/e2e/start-servers.sh index e29f54cc..4be21841 100755 --- a/e2e/start-servers.sh +++ b/e2e/start-servers.sh @@ -28,10 +28,11 @@ cleanup() { } trap cleanup EXIT INT TERM -# Start blit server. --allow-persistent-extensions so a spec can install one: -# a transient `ext run` ends with the CLI connection that started it, so it is -# no use to a spec that wants an extension still serving when the browser looks. -"${REPO_ROOT}/target/debug/blit" server --allow-persistent-extensions & +# Start blit server. Persistent extensions are on by default, which a spec +# needs: a transient `ext run` ends with the CLI connection that started it, so +# it is no use to a spec that wants an extension still serving when the browser +# looks. +"${REPO_ROOT}/target/debug/blit" server & SERVER_PID=$! # Wait for socket to appear diff --git a/extensions/README.md b/extensions/README.md index 6db8a74d..81e7c527 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -99,5 +99,6 @@ blit ext run --persist --restart always systemd extensions/dist/systemd.wasm blit @systemd status ``` -`--persist` needs a server started with `--allow-persistent-extensions`, and it -is also what makes the `@systemd` command namespace available. +`--persist` needs a server that permits persistent extensions, which is the +default (`--no-persistent-extensions` turns it off), and it is also what makes +the `@systemd` command namespace available. diff --git a/extensions/session/README.md b/extensions/session/README.md index c816bd28..f653c638 100644 --- a/extensions/session/README.md +++ b/extensions/session/README.md @@ -28,9 +28,9 @@ being able to look at and its failure count is the only record of that. `forget` drops it: the stored intent is deleted rather than written "off", and what is left is an installed application like any other. -`--persist` requires the operator to have started the server with -`--allow-persistent-extensions`, which is also what makes the intent outlive a -restart. +`--persist` requires a server that permits persistent extensions — the default, +unless the operator passed `--no-persistent-extensions` — which is also what +makes the intent outlive a restart. ## How it starts an application diff --git a/extensions/systemd/src/main.rs b/extensions/systemd/src/main.rs index a371cbad..b1a1c3fd 100644 --- a/extensions/systemd/src/main.rs +++ b/extensions/systemd/src/main.rs @@ -477,7 +477,7 @@ fn run(client: &mut Client) -> Result<(), String> { say( client, "serving blit.systemd.v1; @systemd needs `ext run --persist systemd` \ - on a server started with --allow-persistent-extensions", + on a server that permits persistent extensions", ); 0 }; diff --git a/nix/nixos-module.nix b/nix/nixos-module.nix index 3a688fe2..2971d396 100644 --- a/nix/nixos-module.nix +++ b/nix/nixos-module.nix @@ -93,14 +93,15 @@ in extensions = { persistent = mkOption { type = types.bool; - default = false; + default = true; description = '' Permit durable Wasmi extensions (blit ext run --persist) and start the ones that should be running again after a restart. This is also what makes an extension's - @name command namespace exist. Equivalent to - ; transient extensions run - without it. + @name command namespace exist. Setting it false + passes , which is the + recovery path for a persistent definition that breaks the server it + starts in; transient extensions still run without it. Definitions live in ~/.local/state/blit/extensions.redb and module @@ -442,7 +443,7 @@ in "BLIT_AUDIO_BITRATE=${toString cfg.audio.bitrate}" ] ++ lib.optional (!cfg.audio.enable) "BLIT_AUDIO=0" - ++ lib.optional cfg.extensions.persistent "BLIT_ALLOW_EXT_PERSIST=1"; + ++ lib.optional (!cfg.extensions.persistent) "BLIT_ALLOW_EXT_PERSIST=0"; }; }; }) cfg.users diff --git a/process-compose.yml b/process-compose.yml index 950790bb..4c449c6a 100644 --- a/process-compose.yml +++ b/process-compose.yml @@ -65,12 +65,13 @@ processes: # positive while the new server is still spawning its compositor # and audio pipeline — causing dependents (gateway, share) to # connect to a dead socket and enter a restart loop. - # --allow-persistent-extensions so `blit ext run --persist` works here. - # Without it an extension still runs, but nothing registers its `@name` - # commands, which shows up as `blit ext commands` being silently empty. + # Persistent extensions are on by default, which is what makes `blit ext + # run --persist` work here; with them off an extension still runs, but + # nothing registers its `@name` commands, which shows up as `blit ext + # commands` being silently empty. # The catalog path is per instance so two dev stacks cannot fight over one # redb file -- the loser of that race gets no extension subsystem at all. - command: rm -f ${BLIT_DEV_SOCK:-/tmp/blit-dev.sock} && exec ./target/profiling/blit server --verbose --socket ${BLIT_DEV_SOCK:-/tmp/blit-dev.sock} --allow-persistent-extensions + command: rm -f ${BLIT_DEV_SOCK:-/tmp/blit-dev.sock} && exec ./target/profiling/blit server --verbose --socket ${BLIT_DEV_SOCK:-/tmp/blit-dev.sock} environment: - "BLIT_EXTENSION_PATH=${BLIT_DEV_EXT_DB:-/tmp/blit-dev-ext/extensions.redb}" depends_on: