use anyhow::{Context, Result};
use std::path::Path;
use systemprompt_cloud::ProjectContext;
use systemprompt_identifiers::ProviderId;
use systemprompt_logging::CliService;
use systemprompt_models::profile::{SecretsConfig, SecretsSource, SecretsValidationMode};
use systemprompt_models::services::SystemAdminConfig;
use systemprompt_models::{
CliPaths, CloudConfig, CloudValidationMode, Environment, ExtensionsConfig, Profile,
ProfileDatabaseConfig, ProfileType, RateLimitsConfig, SiteConfig,
};
use super::profile_sections as sections;
use super::secrets::SecretsData;
use crate::shared::profile::generate_display_name;
fn determine_environment(env_name: &str) -> Environment {
match env_name.to_lowercase().as_str() {
"prod" | "production" => Environment::Production,
"staging" | "stage" => Environment::Staging,
"test" | "testing" => Environment::Test,
_ => Environment::Development,
}
}
pub(super) struct ProfileBuildParams<'a> {
pub env_name: &'a str,
pub secrets_path: &'a str,
pub project_root: &'a Path,
pub bin_path: Option<&'a Path>,
pub secrets: &'a SecretsData,
pub default_provider: Option<&'a ProviderId>,
}
pub(super) fn build(params: &ProfileBuildParams<'_>) -> Result<Profile> {
let ProfileBuildParams {
env_name,
secrets_path,
project_root,
bin_path,
secrets,
default_provider,
} = *params;
let ctx = ProjectContext::new(project_root.to_path_buf());
let runtime_env = determine_environment(env_name);
let is_prod = matches!(runtime_env, Environment::Production);
let server = sections::server(is_prod);
let governance = sections::governance(&server.api_internal_url);
let profile = Profile {
name: env_name.to_owned(),
display_name: generate_display_name(env_name),
target: ProfileType::Local,
site: SiteConfig {
name: "systemprompt.io".to_owned(),
github_link: None,
},
database: ProfileDatabaseConfig {
db_type: "postgres".to_owned(),
external_db_access: false,
},
server,
paths: sections::paths(project_root, bin_path, &ctx),
security: sections::security(env_name),
rate_limits: RateLimitsConfig {
disabled: !is_prod,
..Default::default()
},
runtime: sections::runtime(runtime_env, is_prod),
cloud: Some(CloudConfig {
tenant_id: None,
validation: CloudValidationMode::Skip,
}),
secrets: Some(SecretsConfig {
secrets_path: secrets_path.to_owned(),
validation: SecretsValidationMode::Warn,
source: SecretsSource::File,
}),
extensions: ExtensionsConfig::default(),
providers: sections::providers(secrets),
gateway: Some(sections::gateway(secrets, default_provider)),
governance: Some(governance),
system_admin: SystemAdminConfig {
username: "admin".to_owned(),
},
};
profile
.validate()
.context("generated profile failed validation")?;
Ok(profile)
}
pub(super) fn save(profile: &Profile, profile_path: &Path) -> Result<()> {
let header = format!(
"# systemprompt.io Profile: {}\n#\n# Generated by 'systemprompt setup'\n#\n# WARNING: \
This file contains database credentials.\n# DO NOT commit this file to version control.",
profile.display_name
);
crate::shared::profile::save_profile_yaml(profile, profile_path, Some(&header))?;
CliService::success(&format!("Saved profile to {}", profile_path.display()));
Ok(())
}
pub(super) fn profile_dir(systemprompt_dir: &Path, env_name: &str) -> std::path::PathBuf {
systemprompt_dir.join("profiles").join(env_name)
}
pub(super) fn default_path(systemprompt_dir: &Path, env_name: &str) -> std::path::PathBuf {
profile_dir(systemprompt_dir, env_name).join("profile.yaml")
}
pub(super) fn run_migrations(profile_path: &Path) -> Result<()> {
CliService::info("Running database migrations...");
let current_exe = std::env::current_exe().context("Failed to get executable path")?;
let profile_path_str = profile_path.to_string_lossy();
let output = std::process::Command::new(¤t_exe)
.args(CliPaths::db_migrate_args())
.env("SYSTEMPROMPT_PROFILE", profile_path_str.as_ref())
.output()
.context("Failed to run migrations")?;
if output.status.success() {
CliService::success("Migrations completed successfully");
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().filter(|l| !l.is_empty()) {
CliService::info(&format!(" {}", line));
}
return Ok(());
}
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
CliService::error("Migrations failed");
if !stdout.is_empty() {
CliService::info(&stdout);
}
if !stderr.is_empty() {
CliService::error(&stderr);
}
CliService::info("Run manually with:");
CliService::info(&format!(
" SYSTEMPROMPT_PROFILE={} systemprompt {}",
profile_path_str,
CliPaths::db_migrate_cmd()
));
Ok(())
}