use std::path::Path;
use anyhow::{Context, Result};
use tracing::{debug, info};
use crate::CloudflareClient;
pub async fn ensure_r2_custom_domain(
workspace_root: &Path,
provider_id: &str,
bucket_name: &str,
domain: &str,
) -> Result<()> {
let cf_provider = super::cf_creds::CfProvider::resolve(workspace_root, provider_id)?;
let account_id = cf_provider.account_id.clone();
let cf = CloudflareClient::new(cf_provider.api_token()?);
let existing = cf
.list_r2_custom_domains(&account_id, bucket_name)
.await
.with_context(|| format!("listing R2 custom domains on bucket {bucket_name:?}"))?;
if existing.iter().any(|d| d.domain == domain) {
debug!(
domain,
bucket_name, "R2 custom domain already bound — skipping"
);
return Ok(());
}
let zone_name = parent_zone_name(domain);
let zone_id = cf
.zone_id_for_name(zone_name)
.await
.with_context(|| format!("resolving zone id for {zone_name:?}"))?;
cf.add_r2_custom_domain(&account_id, bucket_name, domain, &zone_id)
.await
.with_context(|| format!("binding R2 custom domain {domain:?} → bucket {bucket_name:?}"))?;
info!(domain, bucket_name, zone_name, "R2 custom domain bound");
Ok(())
}
fn parent_zone_name(domain: &str) -> &str {
let last_dot = domain.rfind('.');
let Some(last_dot) = last_dot else {
return domain; };
if let Some(prev_dot) = domain[..last_dot].rfind('.') {
&domain[prev_dot + 1..]
} else {
domain }
}
use crate::config::{DomainConfig, DomainRoute, FrontDoor, RouteMode};
use crate::provider::cloudflare::WorkerBinding;
use crate::reconciler::mesofact_static::WORKER_SCRIPT;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DomainWorkerPlan {
pub worker_name: String,
pub custom_domain: String,
pub asset_origin: String,
pub bindings: Vec<(String, String)>,
}
fn static_worker_bindings(asset_origin: &str, route_headers: String) -> Vec<(String, String)> {
vec![
("ASSET_ORIGIN".to_string(), asset_origin.to_string()),
("UPLOAD_ORIGIN".to_string(), String::new()),
("WORKER_MODE".to_string(), "static".to_string()),
("SSR_ORIGIN".to_string(), String::new()),
("SSR_PREFIXES".to_string(), "[]".to_string()),
("ROUTE_HEADERS".to_string(), route_headers),
]
}
pub fn plan_domain_worker(
domain: &DomainConfig,
cdn_base: &str,
env: &str,
) -> Result<DomainWorkerPlan> {
if domain.front_door != FrontDoor::Worker {
anyhow::bail!(
"domain {} ({}) declares front_door = \"{}\" — only \"worker\" \
domains get a generated Cloudflare Worker",
domain.name,
domain.domain,
domain.front_door.as_str()
);
}
let component = domain
.routes
.iter()
.find_map(|r| match &r.mode {
RouteMode::Static { component } => Some(component.as_str()),
_ => None,
})
.with_context(|| {
format!(
"domain {} ({}) has no `static` route — nothing for a static Worker to serve",
domain.name, domain.domain
)
})?;
let service = component
.split_once('/')
.map(|(svc, _)| svc)
.with_context(|| {
format!(
"domain {}: route component {component:?} — expected \"<service>/<component-id>\"",
domain.name
)
})?;
let asset_origin = format!("{}/{}/{}", cdn_base.trim_end_matches('/'), service, env);
Ok(DomainWorkerPlan {
worker_name: domain.name.clone(),
custom_domain: domain.domain.clone(),
bindings: static_worker_bindings(&asset_origin, domain.route_headers_json()),
asset_origin,
})
}
pub async fn deploy_domain_worker(
workspace_root: &Path,
provider_id: &str,
plan: &DomainWorkerPlan,
) -> Result<()> {
let cf_provider = super::cf_creds::CfProvider::resolve(workspace_root, provider_id)?;
let account_id = cf_provider.account_id.clone();
let cf = CloudflareClient::new(cf_provider.api_token()?);
let worker_bindings: Vec<WorkerBinding<'_>> = plan
.bindings
.iter()
.map(|(k, v)| WorkerBinding::PlainText {
name: k.as_str(),
text: v.as_str(),
})
.collect();
cf.deploy_worker_script(
&account_id,
&plan.worker_name,
WORKER_SCRIPT,
&worker_bindings,
)
.await
.with_context(|| format!("deploying static Worker {}", plan.worker_name))?;
info!(worker = %plan.worker_name, "alias-tier Worker deployed");
let zone = parent_zone_name(&plan.custom_domain);
let zone_id = cf
.zone_id_for_name(zone)
.await
.with_context(|| format!("resolving zone id for {zone:?}"))?;
cf.upsert_worker_custom_domain(
&account_id,
&zone_id,
&plan.custom_domain,
&plan.worker_name,
)
.await
.with_context(|| {
format!(
"binding {} to Worker {}",
plan.custom_domain, plan.worker_name
)
})?;
info!(domain = %plan.custom_domain, worker = %plan.worker_name, "alias-tier custom domain bound");
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AliasTier {
Com,
Net,
}
impl AliasTier {
pub fn zone(self) -> &'static str {
match self {
AliasTier::Com => "com.yah.dev",
AliasTier::Net => "net.yah.dev",
}
}
pub fn bucket(self) -> &'static str {
match self {
AliasTier::Com => "com-yah-dev",
AliasTier::Net => "net-yah-dev",
}
}
fn slug(self) -> &'static str {
match self {
AliasTier::Com => "com",
AliasTier::Net => "net",
}
}
}
pub fn valid_subdomain_label(name: &str) -> bool {
!name.is_empty()
&& name.len() <= 63
&& !name.starts_with('-')
&& !name.ends_with('-')
&& name
.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
}
pub fn plan_alias_claim(
tier: AliasTier,
name: &str,
component: &str,
existing: &BTreeMap<String, DomainConfig>,
) -> Result<DomainConfig> {
if !valid_subdomain_label(name) {
anyhow::bail!(
"invalid subdomain {name:?} — must be 1–63 chars, lowercase \
alphanumeric or hyphen, no leading/trailing hyphen"
);
}
if component.split_once('/').is_none() {
anyhow::bail!("component {component:?} — expected \"<service>/<component-id>\"");
}
let domain = format!("{name}.{}", tier.zone());
let stem = format!("{name}-{}-yah-dev", tier.slug());
if existing.values().any(|d| d.domain == domain) {
anyhow::bail!("{domain} is already claimed");
}
if existing.contains_key(&stem) {
anyhow::bail!("domain manifest {stem:?} already exists");
}
Ok(DomainConfig {
schema_version: 1,
name: stem,
domain,
front_door: FrontDoor::Worker,
cdn_bucket: tier.bucket().to_string(),
worker_bundle_path: None,
routes: vec![DomainRoute {
path: "/*".into(),
headers: BTreeMap::new(),
mode: RouteMode::Static {
component: component.to_string(),
},
}],
})
}
#[cfg(test)]
mod tests {
use super::parent_zone_name;
#[test]
fn parent_zone_strips_one_label_off_subdomain() {
assert_eq!(parent_zone_name("cdn.yah.dev"), "yah.dev");
assert_eq!(parent_zone_name("app.yah.dev"), "yah.dev");
}
#[test]
fn parent_zone_returns_self_for_apex() {
assert_eq!(parent_zone_name("yah.dev"), "yah.dev");
}
#[test]
fn parent_zone_strips_only_first_label_for_deeper_subdomain() {
assert_eq!(parent_zone_name("a.b.yah.dev"), "yah.dev");
}
use super::{plan_domain_worker, DomainWorkerPlan};
use crate::config::{DomainConfig, DomainRoute, FrontDoor, RouteMode};
fn net_tier_manifest() -> DomainConfig {
DomainConfig {
schema_version: 1,
name: "scrabcake-net-yah-dev".into(),
domain: "scrabcake.net.yah.dev".into(),
front_door: FrontDoor::Worker,
cdn_bucket: "net-yah-dev".into(),
worker_bundle_path: None,
routes: vec![DomainRoute {
headers: Default::default(),
path: "/*".into(),
mode: RouteMode::Static {
component: "scrabcake/site".into(),
},
}],
}
}
#[test]
fn plan_resolves_worker_name_domain_and_asset_origin() {
let plan =
plan_domain_worker(&net_tier_manifest(), "https://cdn.net.yah.dev", "cloud").unwrap();
assert_eq!(
plan,
DomainWorkerPlan {
worker_name: "scrabcake-net-yah-dev".into(),
custom_domain: "scrabcake.net.yah.dev".into(),
asset_origin: "https://cdn.net.yah.dev/scrabcake/cloud".into(),
bindings: vec![
(
"ASSET_ORIGIN".into(),
"https://cdn.net.yah.dev/scrabcake/cloud".into()
),
("UPLOAD_ORIGIN".into(), String::new()),
("WORKER_MODE".into(), "static".into()),
("SSR_ORIGIN".into(), String::new()),
("SSR_PREFIXES".into(), "[]".into()),
("ROUTE_HEADERS".into(), "[]".into()),
],
}
);
}
#[test]
fn plan_carries_declared_route_headers_into_the_bindings() {
let mut dom = net_tier_manifest();
dom.routes[0].headers = [
("Cross-Origin-Opener-Policy".to_string(), "same-origin".to_string()),
(
"Cross-Origin-Embedder-Policy".to_string(),
"require-corp".to_string(),
),
]
.into_iter()
.collect();
let plan = plan_domain_worker(&dom, "https://cdn.net.yah.dev", "cloud").unwrap();
let binding = plan
.bindings
.iter()
.find(|(k, _)| k == "ROUTE_HEADERS")
.expect("ROUTE_HEADERS binding");
assert!(binding.1.contains("same-origin"), "{}", binding.1);
assert!(binding.1.contains("require-corp"), "{}", binding.1);
assert!(binding.1.contains("/*"), "{}", binding.1);
}
#[test]
fn plan_trims_trailing_slash_on_cdn_base() {
let plan =
plan_domain_worker(&net_tier_manifest(), "https://cdn.net.yah.dev/", "cloud").unwrap();
assert_eq!(plan.asset_origin, "https://cdn.net.yah.dev/scrabcake/cloud");
}
#[test]
fn plan_bails_when_front_door_is_not_worker() {
for door in [FrontDoor::BucketDirect, FrontDoor::Passway] {
let mut dom = net_tier_manifest();
dom.front_door = door;
let err = plan_domain_worker(&dom, "https://cdn.net.yah.dev", "cloud").unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("front_door"), "{msg}");
assert!(msg.contains(door.as_str()), "{msg}");
}
}
#[test]
fn plan_bails_when_no_static_route() {
let mut dom = net_tier_manifest();
dom.routes = vec![DomainRoute {
headers: Default::default(),
path: "/old".into(),
mode: RouteMode::Redirect {
target: "https://elsewhere".into(),
status: 308,
},
}];
let err = plan_domain_worker(&dom, "https://cdn.net.yah.dev", "cloud").unwrap_err();
assert!(
format!("{err:#}").contains("no `static` route"),
"got: {err:#}"
);
}
use super::{plan_alias_claim, valid_subdomain_label, AliasTier};
use std::collections::BTreeMap;
#[test]
fn label_validation_rules() {
assert!(valid_subdomain_label("scrabcake"));
assert!(valid_subdomain_label("my-repo-1"));
assert!(!valid_subdomain_label("")); assert!(!valid_subdomain_label("-lead")); assert!(!valid_subdomain_label("trail-")); assert!(!valid_subdomain_label("Caps")); assert!(!valid_subdomain_label("under_score")); }
#[test]
fn claim_builds_net_tier_manifest() {
let dom = plan_alias_claim(
AliasTier::Net,
"scrabcake",
"scrabcake/site",
&BTreeMap::new(),
)
.unwrap();
assert_eq!(dom.name, "scrabcake-net-yah-dev");
assert_eq!(dom.domain, "scrabcake.net.yah.dev");
assert_eq!(dom.cdn_bucket, "net-yah-dev");
assert_eq!(dom.routes.len(), 1);
assert!(
matches!(&dom.routes[0].mode, RouteMode::Static { component } if component == "scrabcake/site")
);
}
#[test]
fn claim_uses_com_tier_zone_and_bucket() {
let dom = plan_alias_claim(AliasTier::Com, "acme", "acme/site", &BTreeMap::new()).unwrap();
assert_eq!(dom.domain, "acme.com.yah.dev");
assert_eq!(dom.cdn_bucket, "com-yah-dev");
}
#[test]
fn claim_bails_on_duplicate_host() {
let existing: BTreeMap<String, DomainConfig> =
[("scrabcake-net-yah-dev".to_string(), net_tier_manifest())]
.into_iter()
.collect();
let err =
plan_alias_claim(AliasTier::Net, "scrabcake", "scrabcake/site", &existing).unwrap_err();
assert!(format!("{err:#}").contains("already"), "got: {err:#}");
}
#[test]
fn claim_bails_on_invalid_label() {
let err =
plan_alias_claim(AliasTier::Net, "Bad_Name", "x/y", &BTreeMap::new()).unwrap_err();
assert!(
format!("{err:#}").contains("invalid subdomain"),
"got: {err:#}"
);
}
}