diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c46f22c..0ec63274b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed +- The standalone `switchyard-server` now binds to `127.0.0.1` by default. + Existing deployments that accept remote clients must add `--host 0.0.0.0`. + The provided Docker image and `dev-server/switchyard.service` set this explicitly. - **Stage fallback-judge knobs that had no effect are now rejected** — a `stage_router` route no longer loads with a `classifier.classify_trigger` other than `every_request` or with `classifier.message_hash_fallback`. The stage diff --git a/Dockerfile b/Dockerfile index e2d870eab..16c645d0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,4 +29,4 @@ ENV HOME=/tmp USER 1000:1000 EXPOSE 4000 -ENTRYPOINT ["switchyard-server"] +ENTRYPOINT ["switchyard-server", "--host", "0.0.0.0"] diff --git a/benchmark/routing-profiles/deepswe-v11-advisor-gate-luna-sol.toml b/benchmark/routing-profiles/deepswe-v11-advisor-gate-luna-sol.toml index 85df8b4cb..d750a58c1 100644 --- a/benchmark/routing-profiles/deepswe-v11-advisor-gate-luna-sol.toml +++ b/benchmark/routing-profiles/deepswe-v11-advisor-gate-luna-sol.toml @@ -29,7 +29,7 @@ # OpenRouter so the file runs as written with an OpenRouter key. Treat the figures above # as this pair's operating point, not a guarantee for every serving stack. # -# switchyard-server --config benchmark/routing-profiles/deepswe-v11-advisor-gate-luna-sol.toml --port 4000 +# switchyard-server --config benchmark/routing-profiles/deepswe-v11-advisor-gate-luna-sol.toml --host 0.0.0.0 --port 4000 schema_version = 1 diff --git a/benchmark/routing-profiles/deepswe-v11-plan-execute-luna-sol.toml b/benchmark/routing-profiles/deepswe-v11-plan-execute-luna-sol.toml index 24197ac62..40b17a403 100644 --- a/benchmark/routing-profiles/deepswe-v11-plan-execute-luna-sol.toml +++ b/benchmark/routing-profiles/deepswe-v11-plan-execute-luna-sol.toml @@ -29,7 +29,7 @@ # run as written with an `OPENROUTER_API_KEY`. Absolute results may vary by serving # stack. # -# switchyard-server --config benchmark/routing-profiles/deepswe-v11-plan-execute-luna-sol.toml --port 4000 +# switchyard-server --config benchmark/routing-profiles/deepswe-v11-plan-execute-luna-sol.toml --host 0.0.0.0 --port 4000 schema_version = 1 diff --git a/benchmark/routing-profiles/deepswe-v11-stage-router-luna-sol.toml b/benchmark/routing-profiles/deepswe-v11-stage-router-luna-sol.toml index 5a17d5f3b..60815cb14 100644 --- a/benchmark/routing-profiles/deepswe-v11-stage-router-luna-sol.toml +++ b/benchmark/routing-profiles/deepswe-v11-stage-router-luna-sol.toml @@ -38,7 +38,7 @@ # a local configuration reference. # # Run: -# switchyard-server --config benchmark/routing-profiles/deepswe-v11-stage-router-luna-sol.toml --port 4000 +# switchyard-server --config benchmark/routing-profiles/deepswe-v11-stage-router-luna-sol.toml --host 0.0.0.0 --port 4000 # See benchmark/README.md, section "DeepSWE v1.1", for the Pier command. schema_version = 1 diff --git a/crates/switchyard-server/README.md b/crates/switchyard-server/README.md index ce468ef08..5c154daa3 100644 --- a/crates/switchyard-server/README.md +++ b/crates/switchyard-server/README.md @@ -4,6 +4,12 @@ and Anthropic Messages endpoints. A TOML file explicitly defines the LLM clients, targets, and algorithm routes served by the process. +The standalone binary listens on `127.0.0.1` unless `--host` is set. Pass +`--host 0.0.0.0` to accept remote clients. The provided Docker image and +`dev-server/switchyard.service` opt into that address explicitly. The user service +created by `scripts/linux/install.sh` stays on `127.0.0.1`. A later `--host` +argument overrides the container's default. + ```toml # routes.toml schema_version = 1 diff --git a/crates/switchyard-server/src/cli.rs b/crates/switchyard-server/src/cli.rs index d94172fb4..13d82ac2e 100644 --- a/crates/switchyard-server/src/cli.rs +++ b/crates/switchyard-server/src/cli.rs @@ -13,7 +13,7 @@ use switchyard_server::{ ServerRunOptions, ServerState, TlsOptions, run_server, }; -const DEFAULT_HOST: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED); +const DEFAULT_HOST: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); const DEFAULT_PORT: u16 = 4000; /// Command-line arguments accepted by the Rust server binary. @@ -29,7 +29,8 @@ pub(crate) struct ServerArgs { config: PathBuf, /// Host address to bind. - #[arg(long, default_value_t = DEFAULT_HOST)] + // Let a later --host override the Docker ENTRYPOINT default. + #[arg(long, default_value_t = DEFAULT_HOST, overrides_with = "host")] host: IpAddr, /// Port to bind. @@ -108,3 +109,32 @@ pub(crate) async fn run(args: ServerArgs) -> ServerResult<()> { let (state, options) = args.into_runtime()?; run_server(state, options).await } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_host_is_loopback() { + let args = ServerArgs::try_parse_from(["switchyard-server", "--config", "routes.toml"]) + .expect("valid arguments"); + assert_eq!(args.host, IpAddr::V4(Ipv4Addr::LOCALHOST)); + } + + #[test] + fn explicit_host_overrides_the_container_default() { + for host in ["127.0.0.1", "::1", "0.0.0.0"] { + let args = ServerArgs::try_parse_from([ + "switchyard-server", + "--host", + "0.0.0.0", + "--config", + "routes.toml", + "--host", + host, + ]) + .expect("valid explicit host"); + assert_eq!(args.host, host.parse::().expect("IP address")); + } + } +} diff --git a/dev-server/switchyard.service b/dev-server/switchyard.service index 51962a9a1..288c934d2 100644 --- a/dev-server/switchyard.service +++ b/dev-server/switchyard.service @@ -13,6 +13,7 @@ LoadCredential=tls-cert.pem:/etc/switchyard/cert.pem # %d is where systemd put those files ExecStart=/usr/local/bin/switchyard-server \ -p 443 \ + --host 0.0.0.0 \ --config %d/config.toml \ --tls-key %d/tls-key.pem \ --tls-cert %d/tls-cert.pem diff --git a/docs/cli_reference.md b/docs/cli_reference.md index c9374fbb9..7ecb3023e 100644 --- a/docs/cli_reference.md +++ b/docs/cli_reference.md @@ -14,7 +14,7 @@ switchyard-server --config [options] | Option | Default | Purpose | |---|---|---| | `--config PATH` | Required | TOML file defining LLM clients, targets, and algorithm routes. | -| `--host HOST` | `0.0.0.0` | Address on which the server listens. | +| `--host HOST` | `127.0.0.1` | Address on which the server listens. Use `0.0.0.0` for remote clients. | | `-p, --port PORT` | `4000` | Port on which the server listens. | | `--backlog BACKLOG` | `65535` | TCP listen backlog configured before accepting traffic. | | `--shutdown-timeout SHUTDOWN_TIMEOUT` | `30s` | Maximum time active requests may drain during shutdown. | diff --git a/docs/operations/local_evaluation.md b/docs/operations/local_evaluation.md index 6e14d5233..1eeec56c4 100644 --- a/docs/operations/local_evaluation.md +++ b/docs/operations/local_evaluation.md @@ -65,6 +65,13 @@ switchyard-server --config routes.toml --dry-run switchyard-server --config routes.toml --host 127.0.0.1 --port 4000 ``` +The standalone binary binds to `127.0.0.1` by default. For remote clients, +pass `--host 0.0.0.0` explicitly and secure access to the server. The provided +Docker image and `dev-server/switchyard.service` set this option to preserve +network access. The user service created by `scripts/linux/install.sh` binds +to `127.0.0.1`. +A later `--host` argument overrides the image's default. + The dry run prints `server OK: switchyard`. It checks configuration and credential lookup, not whether the provider accepts the key. In a second terminal, check local readiness: