config.rs•8.3 kB
use std::fmt;
use clap::Parser;
use clusters::DbDriverTag;
use common::types::{
ConvexOrigin,
ConvexSite,
};
use keybroker::{
InstanceSecret,
KeyBroker,
DEV_INSTANCE_NAME,
DEV_SECRET,
};
use metrics::SERVER_VERSION_STR;
use model::database_globals::types::StorageTagInitializer;
use serde_json::Value as JsonValue;
use url::Url;
#[derive(Parser, Clone)]
#[clap(version = &**SERVER_VERSION_STR, author = "Convex, Inc. <no-reply@convex.dev>", group(clap::ArgGroup::new("storage").multiple(false)))]
pub struct LocalConfig {
/// File path for SQLite, the file path; for postgres, a server URL.
#[clap(default_value = "convex_local_backend.sqlite3")]
pub db_spec: String,
/// Database driver type.
#[clap(short, long, value_enum, default_value_t = DbDriverTag::Sqlite)]
pub db: DbDriverTag,
/// Host interface to bind to
#[clap(short, long, default_value = "0.0.0.0")]
pub interface: ::std::net::Ipv4Addr,
/// Host port daemon should bind to
#[clap(short, long, default_value = "3210")]
pub port: u16,
/// Host port to bind for Convex HTTP Actions
#[clap(long, default_value = "3211")]
site_proxy_port: u16,
/// Origin of the Convex server, as accessible from the client.
/// e.g. if the client is running on localhost, you can use the default of
/// http://127.0.0.1:${port}.
/// Otherwise, if the client is accessing the backend from the public
/// internet, this would be the public URL of the backend, like
/// https://api.my-app.com .
/// Note the port in this url (usually 443, the https default) need not
/// match the bind port `--port`.
#[clap(long, requires = "convex_site")]
convex_origin: Option<ConvexOrigin>,
/// Origin of the Convex HTTP Actions, as accessible from the client.
/// e.g. if the client is running on localhost, you can use the default of
/// http://127.0.0.1:${site_proxy_port}.
/// Otherwise, if the client is accessing the backend from the public
/// internet, this would be the public URL of the backend, like
/// https://my-app.com .
/// Note the port in this url (usually 443, the https default) need not
/// match the bind port `--site-proxy-port`.
/// If you don't have a separate URL for HTTP Actions, you can use
/// value from `--convex-origin` with a "/http" suffix, like
/// https://api.my-app.com/http .
#[clap(long, requires = "convex_origin")]
convex_site: Option<ConvexSite>,
/// Optional proxy for Actions fetches
///
/// i.e. if doing `await fetch(request)` within an action, you can
/// send the request through this proxy to screen it for SSRF attacks.
#[clap(long)]
pub convex_http_proxy: Option<Url>,
/// Instance name for this backend.
#[clap(long, requires = "instance_secret")]
pub instance_name: Option<String>,
/// Instance secret for this backend.
#[clap(long, requires = "instance_name")]
pub instance_secret: Option<String>,
/// Identifier (like a user ID) to attach to any sentry
/// events generated by this backend. Sentry is disabled
/// by default.
#[clap(long, hide = true)]
pub sentry_identifier: Option<String>,
/// Which directory should file storage use
#[clap(long, group = "storage", default_value = "convex_local_storage")]
local_storage: String,
/// Use S3 storage instead of local storage.
#[clap(long, group = "storage")]
pub s3_storage: bool,
/// If set, the persistence won't require SSL when talking to the database.
/// It would still prefer SSL if available. This should only be set in
/// tests.
#[clap(long)]
pub do_not_require_ssl: bool,
/// self-hosted Convex will periodically communicate with a remote beacon
/// server. This is to help Convex understand and improve the product.
/// If set, the self-host beacon will not be sent.
#[clap(long, env = "DISABLE_BEACON", value_parser = clap::builder::BoolishValueParser::new())]
pub disable_beacon: bool,
/// A tag to identify the self-hosted instance.
#[clap(long, hide = true, default_value = "self-host")]
pub beacon_tag: String,
/// Extra fields to send to the beacon.
#[clap(long, hide = true)]
pub beacon_fields: Option<JsonValue>,
/// If set, logs will be redacted from clients. Set this on production
/// deployments, to prevent information like stacktraces of serverside
/// code from being leaked to clients.
///
/// On development deployments, it can be helpful to have this information
/// reach the client for debugging purposes.
#[clap(long, default_value = "false")]
pub redact_logs_to_client: bool,
/// Path of local file for logs to be routed to. For local testing
/// of log integrations (eg axiom/datadog).
#[clap(long)]
pub local_log_sink: Option<String>,
}
impl fmt::Debug for LocalConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Config")
.field("convex_origin", &self.convex_origin)
.field("convex_site", &self.convex_site)
.field("instance_name", &self.instance_name)
.finish()
}
}
impl LocalConfig {
pub fn http_bind_address(&self) -> ([u8; 4], u16) {
(self.interface.octets(), self.port)
}
pub fn site_forward_prefix(&self) -> String {
format!("http://127.0.0.1:{}/http", self.port)
}
pub fn site_bind_address(&self) -> Option<([u8; 4], u16)> {
Some((self.interface.octets(), self.site_proxy_port))
}
pub fn convex_origin_url(&self) -> anyhow::Result<ConvexOrigin> {
let origin = self
.convex_origin
.clone()
.unwrap_or(format!("http://127.0.0.1:{}", self.port).into());
// Allow empty origin so you can start up a self-hosted backend without
// knowing its url yet.
if !origin.is_empty() && !origin.starts_with("https://") && !origin.starts_with("http://") {
anyhow::bail!(
"Origin url should start with https:// or http:// but got '{}'",
origin
);
}
Ok(origin)
}
pub fn convex_site_url(&self) -> anyhow::Result<ConvexSite> {
let site = self
.convex_site
.clone()
.unwrap_or(format!("http://127.0.0.1:{}", self.site_proxy_port).into());
// Allow empty site so you can start up a self-hosted backend without
// knowing its url yet.
if !site.is_empty() && !site.starts_with("https://") && !site.starts_with("http://") {
anyhow::bail!(
"Site url should start with https:// or http:// but got '{}'",
site
);
}
Ok(site)
}
pub fn name(&self) -> String {
self.instance_name
.clone()
.unwrap_or(DEV_INSTANCE_NAME.to_owned())
}
pub fn key_broker(&self) -> anyhow::Result<KeyBroker> {
let name = self.name();
KeyBroker::new(&name, self.secret()?)
}
pub fn secret(&self) -> anyhow::Result<InstanceSecret> {
InstanceSecret::try_from(
self.instance_secret
.clone()
.unwrap_or(DEV_SECRET.to_owned())
.as_str(),
)
}
pub fn storage_tag_initializer(&self) -> StorageTagInitializer {
if self.s3_storage {
StorageTagInitializer::S3
} else {
StorageTagInitializer::Local {
dir: self.local_storage.clone().into(),
}
}
}
#[cfg(test)]
pub fn new_for_test() -> anyhow::Result<Self> {
use anyhow::Context;
let tempdir_handle = tempfile::tempdir()?;
let db_path = tempdir_handle.path().join("convex_local_backend.sqlite3");
// Easiest way to get a config object with defaults is to parse from cmd line
let config = Self::try_parse_from([
"convex-local-backend",
db_path.to_str().context("invalid db path")?,
"--local-storage",
tempdir_handle
.path()
.to_str()
.context("invalid local storage path")?,
])?;
Ok(config)
}
}