settings.v

4.5 KB · raw

1//! Machine-wide remote settings.2//!3//! Everything machine-specific lives in `~/.config/koh/`, never inside a repo:4//! one remote per machine, and the S3 keys in a file only the user can read.5//! The format is plain `key=value` lines so it can be read and edited by hand.67module machine89import os1011pub const remote_file = 'remote'12pub const credentials_file = 'credentials'1314pub const kind_folder = 'folder'15pub const kind_s3 = 's3'1617pub struct RemoteSettings {18pub:19	kind     string20	path     string // folder backend21	endpoint string // s3 backend22	bucket   string23	region   string24}2526pub struct Credentials {27pub:28	access_key string29	secret_key string30}3132pub fn save_remote(settings RemoteSettings) ! {33	os.mkdir_all(config_dir(), os.MkdirParams{})!34	mut text := '# written by koh remote — the remote for every repo on this machine\n'35	text += 'kind=${settings.kind}\n'36	if settings.kind == kind_folder {37		text += 'path=${settings.path}\n'38	} else {39		text += 'endpoint=${settings.endpoint}\n'40		text += 'bucket=${settings.bucket}\n'41		text += 'region=${settings.region}\n'42	}43	os.write_file(config_path(remote_file), text)!44}4546pub fn load_remote() !RemoteSettings {47	text := os.read_file(config_path(remote_file)) or {48		return error('this machine has no remote yet — run "koh remote"')49	}50	values := parse_config(text)51	settings := RemoteSettings{52		kind:     values['kind'] or { '' }53		path:     values['path'] or { '' }54		endpoint: values['endpoint'] or { '' }55		bucket:   values['bucket'] or { '' }56		region:   values['region'] or { '' }57	}58	if settings.kind !in [kind_folder, kind_s3] {59		return error('the remote setting in ${config_path(remote_file)} is not readable — run "koh remote" again')60	}61	return settings62}6364pub fn has_remote() bool {65	return os.is_file(config_path(remote_file))66}6768pub fn forget_remote() ! {69	if os.exists(config_path(remote_file)) {70		os.rm(config_path(remote_file))!71	}72}7374// save_credentials writes the keys so that only the user can read them.75pub fn save_credentials(credentials Credentials) ! {76	os.mkdir_all(config_dir(), mode: 0o700)!77	// mkdir_all leaves an existing directory's mode alone.78	os.chmod(config_dir(), 0o700)!79	mut text := '# written by koh remote — keep this file private\n'80	text += 'access_key=${credentials.access_key}\n'81	text += 'secret_key=${credentials.secret_key}\n'82	path := config_path(credentials_file)83	// Created 0600 and renamed over the old file: the keys are never readable84	// by others, not even between a write and a chmod, and a looser old file85	// is replaced rather than rewritten in place.86	temporary := '${path}.tmp-${os.getpid()}'87	// A stale file (or link) by that name would keep its own mode, so it must88	// go; if it cannot, nothing is written.89	if os.exists(temporary) || os.is_link(temporary) {90		os.rm(temporary)!91	}92	mut file := os.open_file(temporary, 'w', 0o600) or {93		os.rm(temporary) or {}94		return err95	}96	os.chmod(temporary, 0o600) or {97		file.close()98		os.rm(temporary) or {}99		return err100	}101	file.write_string(text) or {102		file.close()103		os.rm(temporary) or {}104		return err105	}106	file.close()107	// close() does not report a failed flush, so check what actually landed.108	if (os.read_file(temporary) or { '' }) != text {109		os.rm(temporary) or {}110		return error('could not write ${path}')111	}112	// V's rename, like mv(1), would move the file INTO a directory there.113	if os.is_dir(path) {114		os.rm(temporary) or {}115		return error('${path} is a directory')116	}117	os.rename(temporary, path) or {118		os.rm(temporary) or {}119		return err120	}121}122123pub fn load_credentials() !Credentials {124	path := config_path(credentials_file)125	text := os.read_file(path) or {126		return error('this machine has no S3 keys yet — run "koh remote"')127	}128	// Like ssh with a private key: a hand-written file others can read is129	// refused rather than used.130	if os.stat(path)!.mode & 0o077 != 0 {131		return error('${path} can be read by others — run "chmod 600 ${path}"')132	}133	values := parse_config(text)134	credentials := Credentials{135		access_key: values['access_key'] or { '' }136		secret_key: values['secret_key'] or { '' }137	}138	if credentials.access_key == '' || credentials.secret_key == '' {139		return error('the S3 keys in ${config_path(credentials_file)} are incomplete — run "koh remote" again')140	}141	return credentials142}143144fn parse_config(text string) map[string]string {145	mut values := map[string]string{}146	for line in text.split_into_lines() {147		trimmed := line.trim_space()148		if trimmed == '' || trimmed.starts_with('#') {149			continue150		}151		at := trimmed.index('=') or { continue }152		values[trimmed[..at].trim_space()] = trimmed[at + 1..].trim_space()153	}154	return values155}