use std::sync::Arc;
use std::time::Duration;
use flint_lean::state::SyncerState;
use flint_lean::lease;
use flint_lean::verbs;
use flint_lean::{Access, LeanConfig, LeanError, Syncer, SentinelMode};
use flint_store::s3::S3Store;
use flint_store::ObjectStore;
use warp::Filter;
fn env_req(name: &str) -> String {
std::env::var(name).unwrap_or_else(|_| {
eprintln!("flint-sync: {name} is required");
std::process::exit(2);
})
}
fn flint_lean_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
fn log_retry(sc: &Syncer, e: &LeanError, fallback: &str) {
if !e.is_auth() {
eprintln!("flint-sync: {fallback}: {e}");
return;
}
let paused = sc
.load_gauges()
.ok()
.and_then(|g| g.auth_paused_since_unix)
.map(|t| flint_lean_now().saturating_sub(t))
.unwrap_or(0);
eprintln!(
"flint-sync: REFUSED reason=auth arm={fallback} paused_secs={paused}: {e} \
— the store rejected our credentials. Not contention, not a lease \
conflict; retrying does not fix it. Local files keep serving and \
staged work is intact. Check the credential broker and the projected \
token (and this node's clock — a skewed one answers 403 too). \
Renewals have STOPPED: a challenger that can still reach the store \
may depose this live writer."
);
}
#[cfg(feature = "fastalloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn env_u64(name: &str, default: u64) -> u64 {
std::env::var(name).ok().and_then(|v| v.parse().ok()).unwrap_or(default)
}
#[tokio::main]
async fn main() {
let cmd = std::env::args().nth(1).unwrap_or_else(|| "run".into());
let bucket = env_req("FLINT_SYNC_BUCKET");
let prefix = env_req("FLINT_SYNC_PREFIX");
let root = env_req("FLINT_SYNC_ROOT");
let endpoint = std::env::var("FLINT_SYNC_ENDPOINT").ok();
let part_par = env_u64("FLINT_SYNC_UPLOAD_PART_PARALLELISM", 8).max(1) as usize;
let upload_inflight = env_u64("FLINT_SYNC_UPLOAD_INFLIGHT_MB", 256) * 1024 * 1024;
let copy_whole_max = env_u64("FLINT_SYNC_COPY_WHOLE_MAX_MB", 5 * 1024) * 1024 * 1024;
let raw_reads = std::env::var("FLINT_SYNC_RAW_READS")
.map(|v| v == "true" || v == "1")
.unwrap_or(false);
let store = match S3Store::connect(bucket, endpoint).await {
Ok(s) => match s
.with_part_parallelism(part_par)
.with_upload_inflight_max_bytes(upload_inflight)
.with_copy_whole_max(copy_whole_max)
.with_raw_reads(raw_reads)
{
Ok(s) => Arc::new(s) as Arc<dyn ObjectStore>,
Err(e) => {
eprintln!("flint-sync: raw reads: {e}");
std::process::exit(1);
}
},
Err(e) => {
eprintln!("flint-sync: store connect: {e}");
std::process::exit(1);
}
};
let mut cfg = LeanConfig::new(&prefix, &root);
cfg.floor_secs = env_u64("FLINT_SYNC_FLOOR_SECS", 60);
cfg.sole_writer = std::env::var("FLINT_SYNC_SOLE_WRITER")
.map(|v| v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if let Ok(a) = std::env::var("FLINT_SYNC_ACCESS") {
match Access::parse(&a) {
Some(access) => cfg.access = access,
None => {
eprintln!("flint-sync: FLINT_SYNC_ACCESS={a:?} is not read|readWrite");
std::process::exit(2);
}
}
}
cfg.max_bytes = env_u64("FLINT_SYNC_MAX_BYTES", 0);
cfg.max_files = env_u64("FLINT_SYNC_MAX_FILES", 0);
cfg.fanout = env_u64("FLINT_SYNC_FANOUT", 128).max(1) as usize;
match env_u64("FLINT_SYNC_FETCH_DRIVERS", 0) {
0 => {}
n => cfg.fetch_drivers = n as usize,
}
cfg.upload_fanout = env_u64("FLINT_SYNC_UPLOAD_FANOUT", 32).max(1) as usize;
cfg.project_id = std::env::var("FLINT_SYNC_PROJECT_ID").ok().filter(|p| !p.is_empty());
cfg.fetch_inflight_max_bytes =
env_u64("FLINT_SYNC_FETCH_INFLIGHT_MB", 128).max(1) * 1024 * 1024;
cfg.range_get_min_bytes = env_u64("FLINT_SYNC_RANGE_GET_MIN_MB", 8) * 1024 * 1024;
cfg.range_get_chunk_bytes = env_u64("FLINT_SYNC_RANGE_GET_CHUNK_MB", 16).max(1) * 1024 * 1024;
cfg.range_get_parallelism = env_u64("FLINT_SYNC_RANGE_GET_PARALLELISM", 4).max(1) as usize;
if let Ok(m) = std::env::var("FLINT_SYNC_SENTINELS") {
match SentinelMode::parse(&m) {
Some(sm) => cfg.sentinel_mode = sm,
None => {
eprintln!("flint-sync: FLINT_SYNC_SENTINELS={m:?} is not auto|off|force");
std::process::exit(2);
}
}
}
cfg.sentinel_min_interval_secs = env_u64("FLINT_SYNC_SENTINEL_MIN_INTERVAL_SECS", 5);
cfg.sentinel_hourly_budget = env_u64("FLINT_SYNC_SENTINEL_HOURLY_BUDGET", 60);
cfg.sentinel_poll_secs = env_u64("FLINT_SYNC_SENTINEL_POLL_SECS", 1).max(1);
cfg.drill_hold_commit_secs = env_u64("FLINT_SYNC_DRILL_HOLD_COMMIT_SECS", 0);
cfg.drill_hold_gc_secs = env_u64("FLINT_SYNC_DRILL_HOLD_GC_SECS", 0);
if matches!(std::env::var("FLINT_SYNC_EVENT_TRACE").as_deref(), Ok("1") | Ok("true")) {
cfg.event_trace = Some(flint_lean::trace::Sink::Stderr);
}
if cmd == "ctl" {
let verb = std::env::args().nth(2).unwrap_or_else(|| "status".into());
let (method, path) = match verb.as_str() {
"boundary" => ("POST", "/v1/boundary"),
"sync" => ("POST", "/v1/sync"),
"status" => ("GET", "/v1/status"),
other => {
eprintln!("flint-sync ctl: unknown verb {other:?} (boundary|sync|status)");
std::process::exit(2);
}
};
let sock = flint_lean::uds::socket_path(&cfg.state_dir());
match ctl_call(&sock, method, path).await {
Ok(body) => {
println!("{body}");
return;
}
Err(e) => {
eprintln!("flint-sync ctl: {} ({e})", sock.display());
std::process::exit(1);
}
}
}
if cmd == "manifest" {
match flint_lean::manifest::load(store.as_ref(), &cfg).await {
Ok(Some(m)) => {
let mut heads = serde_json::Map::new();
for (path, e) in &m.manifest.entries {
let head = match store.head(&e.key).await {
Ok(meta) => serde_json::Value::String(meta.etag),
Err(flint_store::StoreError::NotFound(_)) => serde_json::Value::Null,
Err(err) => {
eprintln!("flint-sync: manifest: HEAD {}: {err}", e.key);
std::process::exit(1);
}
};
heads.insert(path.clone(), head);
}
let out = serde_json::json!({
"seq": m.manifest.seq, "pointer_etag": m.etag,
"entries": m.manifest.entries, "heads": heads,
});
println!("{}", serde_json::to_string(&out).unwrap());
return;
}
Ok(None) => {
println!("{}", serde_json::json!({"seq": null, "entries": {}, "heads": {}}));
return;
}
Err(e) => {
eprintln!("flint-sync: manifest: {e}");
std::process::exit(1);
}
}
}
if cmd == "status" {
match flint_lean::status_report(&cfg) {
Ok(r) => {
println!("{}", serde_json::to_string_pretty(&r).unwrap());
return;
}
Err(e) => {
eprintln!("flint-sync: status: {e}");
std::process::exit(1);
}
}
}
let state = match SyncerState::open(cfg.state_dir()) {
Ok(s) => s,
Err(e) => {
eprintln!("flint-sync: state dir: {e}");
std::process::exit(1);
}
};
let mut sc = Syncer { store, cfg, state, lease: None, noted_not_regular: Default::default() };
if matches!(cmd.as_str(), "probe-copy" | "probe-conditional") {
if let Err(e) = sc.refuse_if_read(&cmd) {
eprintln!("flint-sync: {e}");
std::process::exit(flint_lean::EXIT_REFUSED);
}
}
if cmd == "probe-copy" {
let key = format!("{}/{}/probe-copy", sc.cfg.prefix, flint_lean::LEAN_DIR);
eprintln!(
"flint-sync: probe-copy ceiling={} bytes -> the {} arm",
copy_whole_max,
if copy_whole_max == 0 { "MPU + UploadPartCopy" } else { "CopyObject (payload is tiny)" }
);
match flint_store::probe::probe_cross_key_copy(sc.store.as_ref(), &key).await {
Ok(()) => {
eprintln!("flint-sync: probe-copy PASS ({key})");
return;
}
Err(e) => {
eprintln!("flint-sync: probe-copy FAIL: {e}");
std::process::exit(1);
}
}
}
if cmd == "probe-conditional" {
let key = format!("{}/{}/probe-conditional", sc.cfg.prefix, flint_lean::LEAN_DIR);
if let Err(e) = flint_store::probe::probe_conditional_writes(sc.store.as_ref(), &key).await {
eprintln!("flint-sync: probe-conditional FAIL (PUT): {e}");
std::process::exit(1);
}
let dkey = format!("{}/{}/probe-conditional-delete", sc.cfg.prefix, flint_lean::LEAN_DIR);
match flint_store::probe::probe_conditional_delete(sc.store.as_ref(), &dkey).await {
Ok(()) => {
eprintln!("flint-sync: probe-conditional PASS — PUT ({key}) and DELETE ({dkey})");
return;
}
Err(e) => {
eprintln!("flint-sync: probe-conditional FAIL (DELETE): {e}");
std::process::exit(1);
}
}
}
let result = match cmd.as_str() {
"checkout" => verbs::run_verb(&mut sc, verbs::Step::Checkout).await,
"barrier" => verbs::run_verb(&mut sc, verbs::Step::Barrier).await,
"sync" => verbs::run_verb(&mut sc, verbs::Step::Sync).await,
"rescope" => {
let rest: Vec<String> = std::env::args().skip(2).collect();
let target = if rest == ["--all"] {
None
} else if rest.is_empty() {
eprintln!(
"flint-sync: rescope needs a scope (`rescope inputs docs/spec.md`) or \
`--all` for the whole tree"
);
std::process::exit(2);
} else {
Some(rest)
};
verbs::run_verb(&mut sc, verbs::Step::Rescope(target)).await
}
"run" => run_loop(&mut sc).await,
other => {
eprintln!(
"flint-sync: unknown subcommand {other:?} \
(checkout|barrier|sync|rescope|status|manifest|ctl|run|probe-copy|probe-conditional)"
);
std::process::exit(2);
}
};
if let Err(e) = result {
eprintln!("flint-sync: {e}");
std::process::exit(match e {
LeanError::Refused(_) => flint_lean::EXIT_REFUSED,
_ => 1,
});
}
}
async fn ctl_call(
sock: &std::path::Path,
method: &str,
path: &str,
) -> std::io::Result<String> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut s = tokio::net::UnixStream::connect(sock).await?;
s.write_all(format!("{method} {path} HTTP/1.1\r\nhost: flint\r\n\r\n").as_bytes())
.await?;
s.flush().await?;
let mut buf = Vec::new();
s.read_to_end(&mut buf).await?;
let text = String::from_utf8_lossy(&buf).to_string();
Ok(match text.split_once("\r\n\r\n") {
Some((_, body)) => body.to_string(),
None => text,
})
}
async fn run_loop(sc: &mut Syncer) -> Result<(), LeanError> {
lease::verify_claim(sc).await?;
lease::warn_if_prefix_is_shared(sc).await;
if sc.cfg.access.is_read() {
eprintln!(
"flint-sync: read access — this syncer follows the workspace and never writes to the bucket"
);
} else if let Err(e) = lease::release_stale_own(sc).await {
eprintln!("flint-sync: could not release a fence left held by a previous container: {e}");
}
if let Err(e) = sc.state.clear_drained() {
eprintln!("flint-sync: could not clear a stale drain attestation: {e}");
}
let drain_budget = Duration::from_secs(env_u64("FLINT_SYNC_DRAIN_BUDGET_SECS", 6));
let posture = sc.sentinel_preflight()?;
sc.write_capabilities(&posture)?;
if !posture.enabled {
eprintln!(
"flint-sync: sentinel verbs DISABLED ({}) — the poll arm will not arm",
posture.reason.as_deref().unwrap_or("unknown")
);
}
sc.checkout_scoped(verbs::env_list("FLINT_SYNC_CHECKOUT_SCOPE")).await?;
let posture = sc.sentinel_preflight()?;
sc.write_capabilities(&posture)?;
eprintln!("flint-sync: checkout complete — agent may start");
if posture.enabled {
if let Err(e) = sc.settle_pending_at_startup().await {
eprintln!("flint-sync: startup settle failed (retrying at the floor): {e}");
}
}
{
let enabled = std::env::var("FLINT_SYNC_METRICS").ok().as_deref() == Some("true");
let port: u16 = std::env::var("FLINT_SYNC_METRICS_PORT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(9847);
let mut posture =
flint_lean::metrics::MetricsPosture { enabled, port, bound: false, error: None };
if enabled {
let labels = flint_lean::metrics::Labels {
workspace: std::env::var("FLINT_SYNC_WORKSPACE").unwrap_or_else(|_| "unknown".into()),
namespace: std::env::var("FLINT_SYNC_NAMESPACE").unwrap_or_else(|_| "unknown".into()),
};
let state_dir = sc.cfg.state_dir();
let route = warp::get().and(warp::path("metrics")).map(move || {
let g: flint_lean::Gauges =
std::fs::read(state_dir.join("gauges.json"))
.ok()
.and_then(|b| serde_json::from_slice(&b).ok())
.unwrap_or_default();
warp::reply::with_header(
flint_lean::metrics::render(&g, &labels),
"content-type",
"text/plain; version=0.0.4",
)
});
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
match warp::serve(route).try_bind_ephemeral(addr) {
Ok((bound, fut)) => {
eprintln!("flint-sync: /metrics on {bound}");
posture.bound = true;
tokio::spawn(fut);
}
Err(e) => {
eprintln!(
"flint-sync: /metrics NOT exposed on {addr} ({e}) — the workspace is \
unaffected; gauges.json and the lease cell's echo remain authoritative"
);
posture.error = Some(e.to_string());
}
}
}
if let Err(e) = sc.save_metrics_posture(&posture) {
eprintln!("flint-sync: could not record the metrics posture: {e}");
}
}
let mut ctl_rx = if std::env::var("FLINT_SYNC_UDS_DOOR").ok().as_deref() == Some("true") {
let path = flint_lean::uds::socket_path(&sc.cfg.state_dir());
match flint_lean::uds::bind(&path) {
Ok(listener) => {
let (tx, rx) = tokio::sync::mpsc::channel(16);
tokio::spawn(flint_lean::uds::serve(listener, tx));
eprintln!("flint-sync: control socket at {}", path.display());
Some(rx)
}
Err(e) => {
eprintln!("flint-sync: control socket NOT available ({e}) — the file protocol \
is unaffected");
None
}
}
} else {
None
};
let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("SIGTERM handler");
let floor = Duration::from_secs(sc.cfg.floor_secs.max(1));
let poll_every = Duration::from_secs(sc.cfg.sentinel_poll_secs.max(1));
let mut floor_iv = tokio::time::interval(floor);
let mut poll_iv = tokio::time::interval(poll_every);
for iv in [&mut floor_iv, &mut poll_iv] {
iv.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
iv.reset(); }
loop {
tokio::select! {
_ = floor_iv.tick() => {
match sc.floor_tick().await {
Ok(o) if !o.no_change || !o.acks.is_empty() => eprintln!(
"flint-sync: {} seq={:?} up={} del={} consumed={} acks={}{}",
if sc.cfg.access.is_read() { "pull" } else { "barrier" },
o.seq, o.uploaded, o.deleted, o.consumed, o.acks.len(),
match &o.withheld_reason {
Some(why) => format!(" withheld_reason={why}"),
None => String::new(),
}
),
Ok(_) => {}
Err(e) => log_retry(
&sc,
&e,
if sc.cfg.access.is_read() {
"pull failed (retrying next floor)"
} else {
"barrier failed (retrying next floor)"
},
),
}
}
_ = poll_iv.tick() => {
if !posture.enabled { continue; }
match sc.sentinel_tick().await {
Ok(acks) => for a in acks {
eprintln!(
"flint-sync: sentinel ack status={} boundary={} seq={:?} nonces={}",
a.status, a.boundary, a.seq, a.nonces.len()
);
},
Err(e) => log_retry(&sc, &e, "sentinel honor failed (retrying)"),
}
}
Some(req) = async { match ctl_rx.as_mut() { Some(rx) => rx.recv().await, None => None } } => {
match req {
flint_lean::uds::CtlRequest::Boundary { reply, .. } if sc.cfg.access.is_read() => {
let _ = reply.send(serde_json::json!({
"status": "refused-read-only",
"message": "this syncer has read access: nothing in this tree is published",
}));
}
flint_lean::uds::CtlRequest::Boundary { note, reply } => {
let out = async {
sc.request_boundary(&format!("uds:{}", flint_lean_now()), note)?;
sc.sentinel_tick().await
}
.await;
let v = match out {
Ok(acks) => serde_json::json!({
"status": "ok",
"acks": acks.iter().map(|a| serde_json::json!({
"status": a.status, "boundary": a.boundary,
"seq": a.seq, "nonces": a.nonces,
})).collect::<Vec<_>>(),
}),
Err(e) => serde_json::json!({
"status": "error", "message": e.to_string(),
}),
};
let _ = reply.send(v);
}
flint_lean::uds::CtlRequest::Sync { reply } => {
let v = match sc.sync().await {
Ok(r) => serde_json::to_value(&r).unwrap_or_default(),
Err(e) => serde_json::json!({
"status": "error", "message": e.to_string(),
}),
};
let _ = reply.send(v);
}
flint_lean::uds::CtlRequest::Status { reply } => {
let v = match flint_lean::status_report(&sc.cfg) {
Ok(r) => serde_json::to_value(&r).unwrap_or_default(),
Err(e) => serde_json::json!({
"status": "error", "message": e.to_string(),
}),
};
let _ = reply.send(v);
}
}
}
_ = term.recv() => {
eprintln!("flint-sync: SIGTERM — final drain barrier");
let started = std::time::Instant::now();
let mut attempt = 0u32;
let mut last = Ok(vec![]);
loop {
attempt += 1;
last = sc.drain().await;
match &last {
Ok(_) => break,
Err(e) => {
eprintln!("flint-sync: drain attempt {attempt} failed: {e}");
let again = attempt < 3
|| started.elapsed() + Duration::from_secs(2) < drain_budget;
if !again { break; }
tokio::time::sleep(Duration::from_secs(2)).await;
}
}
}
match last {
Ok(acks) => {
let seq = sc.state.load_baseline().ok().map(|b| b.seq);
if let Err(e) = sc.state.write_drained(seq, acks.len()) {
eprintln!("flint-sync: drain published but its attestation could not be written: {e}");
}
return Ok(());
}
Err(e) => {
eprintln!(
"flint-sync: drain FAILED after {attempt} attempts over {}s — no drain \
attestation written; the tree keeps everything since the last boundary",
started.elapsed().as_secs()
);
return Err(e);
}
}
}
}
}
}