use anyhow::{Context, Result, bail};
use systemprompt_config::{ProfileBootstrap, SecretsBootstrap};
use super::{args, bootstrap};
use crate::cli_settings::CliConfig;
use crate::commands::admin;
use crate::descriptor::CommandDescriptor;
use crate::env_overrides::EnvOverrides;
enum RoutingAction {
ContinueLocal,
ExternalDbUrl(String),
}
pub(super) async fn bootstrap_profile(
cli: &args::Cli,
desc: &CommandDescriptor,
cli_config: &CliConfig,
env: &EnvOverrides,
) -> Result<Option<String>> {
let has_export = args::has_local_export_flag(cli.command.as_ref());
let ctx = bootstrap::resolve_and_display_profile(cli_config, env, has_export)?;
enforce_routing_policy(&ctx, cli, desc).await?;
let needs_cloud = is_cloud_bypass_command(cli.command.as_ref());
match initialize_post_routing(&ctx, desc, needs_cloud).await? {
RoutingAction::ExternalDbUrl(url) => Ok(Some(url)),
RoutingAction::ContinueLocal => Ok(None),
}
}
async fn enforce_routing_policy(
ctx: &bootstrap::ProfileContext,
cli: &args::Cli,
desc: &CommandDescriptor,
) -> Result<()> {
if !ctx.env.is_fly && desc.remote_eligible() && !ctx.has_export {
let profile = ProfileBootstrap::get()?;
try_remote_routing(cli, profile).await?;
return Ok(());
}
if ctx.has_export && ctx.is_cloud && !ctx.external_db_access {
bail!(
"Export with cloud profile '{}' requires external database access.\nEnable \
external_db_access in the profile or use a local profile.",
ctx.profile_name
);
}
if ctx.is_cloud
&& !ctx.env.is_fly
&& !ctx.external_db_access
&& !is_cloud_bypass_command(cli.command.as_ref())
{
bail!(
"Cloud profile '{}' selected but this command doesn't support remote execution.\nUse \
a local profile with --profile <name> or enable external database access.",
ctx.profile_name
);
}
Ok(())
}
const fn is_cloud_bypass_command(command: Option<&args::Commands>) -> bool {
matches!(
command,
Some(args::Commands::Cloud(_) | args::Commands::Admin(admin::AdminCommands::Session(_)))
)
}
async fn initialize_post_routing(
ctx: &bootstrap::ProfileContext,
desc: &CommandDescriptor,
needs_cloud: bool,
) -> Result<RoutingAction> {
if needs_cloud || (ctx.is_cloud && ctx.external_db_access) {
bootstrap::init_credentials_gracefully(needs_cloud).await?;
}
if desc.secrets() {
bootstrap::init_secrets()?;
}
if ctx.is_cloud && ctx.external_db_access && desc.paths() && !ctx.env.is_fly {
let secrets = SecretsBootstrap::get().context("Secrets required for external DB access")?;
let db_url = secrets.effective_database_url(true).to_owned();
return Ok(RoutingAction::ExternalDbUrl(db_url));
}
if desc.paths() {
bootstrap::init_paths()?;
if !desc.skip_validation() {
bootstrap::run_validation()?;
}
}
if !ctx.is_cloud {
bootstrap::validate_cloud_credentials(&ctx.env);
}
Ok(RoutingAction::ContinueLocal)
}
async fn try_remote_routing(cli: &args::Cli, profile: &systemprompt_models::Profile) -> Result<()> {
use super::routing;
let is_cloud = profile.target.is_cloud();
match routing::determine_execution_target() {
Ok(routing::ExecutionTarget::Remote {
hostname,
token,
context,
}) => {
let args = args::reconstruct_args(cli);
let exit_code =
routing::execute_remote(&hostname, token.as_str(), context.as_str(), &args, 300)
.await?;
if exit_code != 0 {
bail!("Remote command exited with code {}", exit_code);
}
return Ok(());
},
Ok(routing::ExecutionTarget::Local) if is_cloud => {
require_external_db_access(profile, "no tenant is configured")?;
},
Err(e) if is_cloud => {
require_external_db_access(profile, &format!("routing failed: {}", e))?;
},
_ => {},
}
Ok(())
}
fn require_external_db_access(profile: &systemprompt_models::Profile, reason: &str) -> Result<()> {
if profile.database.external_db_access {
tracing::debug!(
profile_name = %profile.name,
reason = reason,
"Cloud profile allowing local execution via external_db_access"
);
Ok(())
} else {
bail!(
"Cloud profile '{}' requires remote execution but {}.\nRun 'systemprompt admin \
session login' to authenticate.",
profile.name,
reason
)
}
}