remote.v

2.6 KB · raw

1//! The remote interface: five operations, two backends.2//!3//! A remote is dumb storage that can do one safe swap. Everything clever —4//! what to upload, what diverged, how to combine — lives in `src/exchange`, above5//! this interface, so the folder and S3 backends stay thin and the sync logic6//! is written once and tested offline.78module remote910import rand11import os1213// get not finding a key is a normal answer, not an error, so `Object.found`14// carries it and errors mean real trouble (a refused connection, bad keys).15pub struct Object {16pub:17	found bool18	body  []u819	tag   string20}2122// Backend is one place to put bytes. `put_if_absent` never overwrites and23// `put_if_match` only overwrites when the version still matches, which is what24// makes two machines offering at once safe.25pub interface Backend {26mut:27	get(key string) !Object28	list(prefix string) ![]string29	put_if_absent(key string, body []u8) !bool30	put_if_match(key string, body []u8, tag string) !bool31	delete(key string) !32}3334pub const probe_key = 'koh-probe'3536// check runs the connection test `koh remote` insists on before saving a37// setting: write a probe object, confirm a second write of it is refused, then38// delete it. A backend that cannot do that cannot keep two machines honest.39pub fn check(mut backend Backend) ! {40	payload := 'koh probe'.bytes()41	// Each check has its own key: with a shared one, two machines setting up42	// at once delete each other's probe and report a broken remote.43	key := '${probe_key}-${rand.hex(16)}'44	if !backend.put_if_absent(key, payload)! {45		return error('the remote refused to store a new object')46	}47	defer {48		// Also on the early returns below; deleting twice is harmless.49		backend.delete(key) or {}50	}51	if backend.put_if_absent(key, payload)! {52		return error('the remote overwrote an existing object — conditional writes are not working')53	}54	probe := backend.get(key)!55	if !probe.found || probe.body != payload {56		return error('the remote did not give back what was written')57	}58	// Strip needs delete, so a remote that cannot delete fails here.59	backend.delete(key)!60}6162/// Find the system CA bundle for HTTPS; plain HTTP needs no TLS roots.63pub fn tls_roots(url string) !string {64	mut roots := ''65	if url.starts_with('https://') {66		for path in [os.getenv('SSL_CERT_FILE'), '/etc/ssl/cert.pem',67			'/etc/ssl/certs/ca-certificates.crt', '/etc/ssl/certs/ca-bundle.crt',68			'/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem'] {69			if path != '' && os.is_file(path) {70				roots = path71				break72			}73		}74		if roots == '' {75			return error('no system CA bundle found — set SSL_CERT_FILE to your trusted CA bundle')76		}77	}78	return roots79}