Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions crates/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions crates/server/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion crates/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,9 @@ pub struct Config {
pub allow_forward: Vec<String>,
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,
}

Expand Down
7 changes: 3 additions & 4 deletions docs/design/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 18 additions & 13 deletions docs/design/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2385,31 +2385,37 @@ 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
available because they change only the caller's observation cursor. Operations which could install or
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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/systemd-user-units.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<id>` 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/<id>` in the
server's KV store and replayed at startup, so the manager comes up with the
session from then on.

Expand Down
9 changes: 5 additions & 4 deletions e2e/start-servers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 3 additions & 3 deletions extensions/session/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion extensions/systemd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
11 changes: 6 additions & 5 deletions nix/nixos-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,15 @@ in
extensions = {
persistent = mkOption {
type = types.bool;
default = false;
default = true;
description = ''
Permit durable Wasmi extensions (<literal>blit ext run
--persist</literal>) and start the ones that should be running again
after a restart. This is also what makes an extension's
<literal>@name</literal> command namespace exist. Equivalent to
<option>BLIT_ALLOW_EXT_PERSIST=1</option>; transient extensions run
without it.
<literal>@name</literal> command namespace exist. Setting it false
passes <option>BLIT_ALLOW_EXT_PERSIST=0</option>, 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
<filename>~/.local/state/blit/extensions.redb</filename> and module
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions process-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading