worklyn / zukapublic
Agent-first git hosting. One Rust binary: git over HTTP and SSH, a REST API, MCP, CI, and multi-tenant isolation.
Get a copy:
git clone https://zuka.worklyn.com/worklyn/zuka.git
| 1 | // Product identity, in one place. |
| 2 | // |
| 3 | // Everything that carries the product's name — the environment-variable prefix, the |
| 4 | // default data directory, the problem-type URIs, the auth realm, the git config |
| 5 | // namespace, the CI filename, the hook shim, user-facing message prefixes — derives |
| 6 | // from `NAME` rather than repeating it as a literal. |
| 7 | // |
| 8 | // This module has already earned itself: the product was renamed once, and the edit |
| 9 | // was one line in `Cargo.toml` plus a directory move. Before it existed the name was |
| 10 | // fifty string literals across the tree — the kind of change that is technically |
| 11 | // easy and practically never done. |
| 12 | |
| 13 | use std::sync::OnceLock; |
| 14 | |
| 15 | /// The product name. Taken from the crate so the two cannot disagree. |
| 16 | pub const NAME: &str = env!("CARGO_PKG_NAME"); |
| 17 | |
| 18 | /// The version a person reads. |
| 19 | /// |
| 20 | /// Major and minor come from Cargo.toml and are set deliberately. The third |
| 21 | /// component is the CI build number, which grows on its own — see `build.rs`. |
| 22 | pub const VERSION: &str = env!("ZUKA_BUILD_VERSION"); |
| 23 | |
| 24 | /// Commit this binary was built from, from `build.rs`. |
| 25 | pub const COMMIT: &str = env!("ZUKA_BUILD_COMMIT"); |
| 26 | |
| 27 | /// The identity upgrade detection compares: version plus commit. |
| 28 | /// |
| 29 | /// Not the version alone. A forgotten bump would make an old tenant look current, |
| 30 | /// and the failure mode of that is a security patch silently not landing — which is |
| 31 | /// exactly the case the automatic upgrade exists for. |
| 32 | pub fn build() -> &'static str { |
| 33 | static BUILD: OnceLock<String> = OnceLock::new(); |
| 34 | BUILD.get_or_init(|| format!("{VERSION}+{COMMIT}")) |
| 35 | } |
| 36 | |
| 37 | /// Uppercase name, cached. Used to build the environment-variable prefix. |
| 38 | fn upper() -> &'static str { |
| 39 | static UPPER: OnceLock<String> = OnceLock::new(); |
| 40 | UPPER.get_or_init(|| NAME.to_ascii_uppercase()) |
| 41 | } |
| 42 | |
| 43 | /// Prefix on every environment variable this service reads, e.g. `ZUKA_`. |
| 44 | pub fn env_prefix() -> &'static str { |
| 45 | static PREFIX: OnceLock<String> = OnceLock::new(); |
| 46 | PREFIX.get_or_init(|| format!("{}_", upper())) |
| 47 | } |
| 48 | |
| 49 | /// Full name of a namespaced environment variable. |
| 50 | pub fn env_name(leaf: &str) -> String { |
| 51 | format!("{}{leaf}", env_prefix()) |
| 52 | } |
| 53 | |
| 54 | /// Read a namespaced environment variable. |
| 55 | /// |
| 56 | /// Call sites name the suffix (`"BIND"`), never the whole variable, so the prefix is |
| 57 | /// defined once. |
| 58 | pub fn env(leaf: &str) -> Option<String> { |
| 59 | std::env::var(env_name(leaf)).ok() |
| 60 | } |
| 61 | |
| 62 | /// Default data directory, `/var/lib/<name>`. |
| 63 | pub fn default_data_dir() -> &'static str { |
| 64 | static DIR: OnceLock<String> = OnceLock::new(); |
| 65 | DIR.get_or_init(|| format!("/var/lib/{NAME}")) |
| 66 | } |
| 67 | |
| 68 | /// Base for RFC 9457 `type` URIs. |
| 69 | /// |
| 70 | /// Configurable because a self-hoster should be able to point clients at their own |
| 71 | /// documentation rather than at ours. The slugs after the base are the stable part of |
| 72 | /// the contract; the origin is not. |
| 73 | pub fn problem_base() -> &'static str { |
| 74 | static BASE: OnceLock<String> = OnceLock::new(); |
| 75 | BASE.get_or_init(|| { |
| 76 | env("PROBLEM_BASE") |
| 77 | .map(|v| v.trim_end_matches('/').to_string()) |
| 78 | .unwrap_or_else(|| format!("https://{NAME}.dev/problems")) |
| 79 | }) |
| 80 | } |
| 81 | |
| 82 | /// Full problem `type` URI for a slug. |
| 83 | pub fn problem_uri(slug: &str) -> String { |
| 84 | format!("{}/{slug}", problem_base()) |
| 85 | } |
| 86 | |
| 87 | /// HTTP Basic authentication realm. |
| 88 | pub fn realm() -> &'static str { |
| 89 | NAME |
| 90 | } |
| 91 | |
| 92 | /// Prefix for messages printed at a user's terminal by git or ssh. |
| 93 | pub fn say(message: impl std::fmt::Display) -> String { |
| 94 | format!("{NAME}: {message}") |
| 95 | } |
| 96 | |
| 97 | /// Filename of the in-repository CI spec, `.<name>.toml`. |
| 98 | pub fn ci_filename() -> &'static str { |
| 99 | static FILE: OnceLock<String> = OnceLock::new(); |
| 100 | FILE.get_or_init(|| format!(".{NAME}.toml")) |
| 101 | } |
| 102 | |
| 103 | /// An HTTP header in this product's namespace, e.g. `x-zuka-identity`. |
| 104 | pub fn header_name(leaf: &str) -> &'static str { |
| 105 | // Leaked deliberately: header names are `&'static str` throughout hyper, and |
| 106 | // there are a handful of them for the life of the process. |
| 107 | Box::leak(format!("x-{NAME}-{leaf}").into_boxed_str()) |
| 108 | } |
| 109 | |
| 110 | /// A git config key in this product's namespace, e.g. `zuka.protected`. |
| 111 | /// |
| 112 | /// Settings live in the repository's own git config rather than in our metadata so |
| 113 | /// the hooks can read them with no data directory, no environment and no lookup — |
| 114 | /// and so they travel with the repository if it is copied. |
| 115 | pub fn git_config_key(leaf: &str) -> String { |
| 116 | format!("{NAME}.{leaf}") |
| 117 | } |
| 118 | |
| 119 | #[cfg(test)] |
| 120 | mod tests { |
| 121 | use super::*; |
| 122 | |
| 123 | #[test] |
| 124 | fn the_env_prefix_is_derived_from_the_crate_name() { |
| 125 | assert_eq!(env_prefix(), format!("{}_", NAME.to_ascii_uppercase())); |
| 126 | assert_eq!(env_name("BIND"), format!("{}BIND", env_prefix())); |
| 127 | } |
| 128 | |
| 129 | #[test] |
| 130 | fn derived_identifiers_all_track_the_name() { |
| 131 | assert!(default_data_dir().ends_with(NAME)); |
| 132 | assert_eq!(ci_filename(), format!(".{NAME}.toml")); |
| 133 | assert_eq!(header_name("identity"), format!("x-{NAME}-identity")); |
| 134 | assert_eq!(git_config_key("protected"), format!("{NAME}.protected")); |
| 135 | assert_eq!(realm(), NAME); |
| 136 | assert_eq!(say("hello"), format!("{NAME}: hello")); |
| 137 | } |
| 138 | |
| 139 | #[test] |
| 140 | fn the_build_identity_carries_both_the_version_and_the_commit() { |
| 141 | // Version alone is not enough: CI bumps it, and a forgotten bump would make |
| 142 | // an old tenant look current. |
| 143 | let build = build(); |
| 144 | assert!(build.starts_with(VERSION), "{build}"); |
| 145 | assert!(build.contains('+'), "{build}"); |
| 146 | assert!( |
| 147 | build.len() > VERSION.len() + 1, |
| 148 | "the commit must be present: {build}" |
| 149 | ); |
| 150 | } |
| 151 | |
| 152 | #[test] |
| 153 | fn a_problem_uri_is_the_base_plus_the_slug() { |
| 154 | let uri = problem_uri("not-found"); |
| 155 | assert!(uri.starts_with(problem_base())); |
| 156 | assert!(uri.ends_with("/not-found")); |
| 157 | } |
| 158 | |
| 159 | /// No Rust source outside this module may contain the product name as a |
| 160 | /// literal. |
| 161 | /// |
| 162 | /// This is the guard that makes the rename claim true rather than aspirational. |
| 163 | /// It has already caught one leak — an auth realm hardcoded in a header — that |
| 164 | /// a manual rename test surfaced only by inspecting the wire. |
| 165 | #[test] |
| 166 | fn the_name_appears_in_no_other_source_file() { |
| 167 | let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); |
| 168 | let mut offenders = Vec::new(); |
| 169 | |
| 170 | let mut stack = vec![root]; |
| 171 | while let Some(dir) = stack.pop() { |
| 172 | for entry in std::fs::read_dir(&dir).expect("read src") { |
| 173 | let path = entry.expect("dir entry").path(); |
| 174 | if path.is_dir() { |
| 175 | stack.push(path); |
| 176 | continue; |
| 177 | } |
| 178 | if path.extension().and_then(|e| e.to_str()) != Some("rs") { |
| 179 | continue; |
| 180 | } |
| 181 | if path.file_name().and_then(|f| f.to_str()) == Some("brand.rs") { |
| 182 | continue; |
| 183 | } |
| 184 | let body = std::fs::read_to_string(&path).expect("read source"); |
| 185 | for (n, line) in body.lines().enumerate() { |
| 186 | if line.to_ascii_lowercase().contains(NAME) { |
| 187 | offenders.push(format!( |
| 188 | "{}:{}: {}", |
| 189 | path.file_name().unwrap().to_string_lossy(), |
| 190 | n + 1, |
| 191 | line.trim() |
| 192 | )); |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | assert!( |
| 199 | offenders.is_empty(), |
| 200 | "the product name is hardcoded outside brand.rs; derive it instead:\n{}", |
| 201 | offenders.join("\n") |
| 202 | ); |
| 203 | } |
| 204 | |
| 205 | #[test] |
| 206 | fn nothing_here_hardcodes_the_current_name() { |
| 207 | // If the crate is renamed, every derived value must follow. This fails if |
| 208 | // someone reintroduces a literal. |
| 209 | for derived in [ |
| 210 | default_data_dir().to_string(), |
| 211 | ci_filename().to_string(), |
| 212 | git_config_key("x"), |
| 213 | realm().to_string(), |
| 214 | say("x"), |
| 215 | ] { |
| 216 | assert!( |
| 217 | derived.contains(NAME), |
| 218 | "{derived:?} does not track the crate name" |
| 219 | ); |
| 220 | } |
| 221 | } |
| 222 | } |