pg_embedded_setup_unpriv/
lib.rs1mod bootstrap;
9mod cluster;
10mod env;
11mod error;
12mod fs;
13#[cfg(all(
14 unix,
15 any(
16 target_os = "linux",
17 target_os = "android",
18 target_os = "freebsd",
19 target_os = "openbsd",
20 target_os = "dragonfly",
21 ),
22))]
23mod privileges;
24#[doc(hidden)]
25pub mod test_support;
26#[doc(hidden)]
27pub mod worker;
28pub(crate) mod worker_process;
29
30#[doc(hidden)]
32pub mod worker_process_test_api {
33 use std::time::Duration;
34
35 use camino::Utf8Path;
36 use postgresql_embedded::Settings;
37
38 pub use crate::cluster::WorkerOperation;
39 use crate::worker_process;
40
41 #[cfg(all(
42 unix,
43 any(
44 target_os = "linux",
45 target_os = "android",
46 target_os = "freebsd",
47 target_os = "openbsd",
48 target_os = "dragonfly",
49 ),
50 ))]
51 use crate::worker_process::PrivilegeDropGuard as InnerPrivilegeDropGuard;
52
53 pub struct WorkerRequest<'a>(worker_process::WorkerRequest<'a>);
58
59 impl<'a> WorkerRequest<'a> {
60 #[must_use]
61 #[expect(
62 clippy::too_many_arguments,
63 reason = "test helper mirrors worker request constructor"
64 )]
65 pub const fn new(
87 worker: &'a Utf8Path,
88 settings: &'a Settings,
89 env_vars: &'a [(String, Option<String>)],
90 operation: WorkerOperation,
91 timeout: Duration,
92 ) -> Self {
93 Self(worker_process::WorkerRequest::new(
94 worker, settings, env_vars, operation, timeout,
95 ))
96 }
97
98 pub(crate) const fn inner(&self) -> &worker_process::WorkerRequest<'a> {
100 &self.0
101 }
102 }
103
104 pub fn run(request: &WorkerRequest<'_>) -> crate::BootstrapResult<()> {
106 worker_process::run(request.inner())
107 }
108
109 #[cfg(all(
110 unix,
111 any(
112 target_os = "linux",
113 target_os = "android",
114 target_os = "freebsd",
115 target_os = "openbsd",
116 target_os = "dragonfly",
117 ),
118 ))]
119 pub struct PrivilegeDropGuard {
121 _inner: InnerPrivilegeDropGuard,
122 }
123
124 #[cfg(all(
125 unix,
126 any(
127 target_os = "linux",
128 target_os = "android",
129 target_os = "freebsd",
130 target_os = "openbsd",
131 target_os = "dragonfly",
132 ),
133 ))]
134 #[must_use]
135 pub fn disable_privilege_drop_for_tests() -> PrivilegeDropGuard {
138 PrivilegeDropGuard {
139 _inner: worker_process::disable_privilege_drop_for_tests(),
140 }
141 }
142
143 #[must_use]
144 pub fn render_failure_for_tests(
146 context: &str,
147 output: &std::process::Output,
148 ) -> crate::BootstrapError {
149 worker_process::render_failure_for_tests(context, output)
150 }
151}
152
153#[doc(hidden)]
154pub use crate::env::ScopedEnv;
155pub use bootstrap::{
156 ExecutionMode, ExecutionPrivileges, TestBootstrapEnvironment, TestBootstrapSettings,
157 bootstrap_for_tests, detect_execution_privileges, run,
158};
159#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
160#[doc(hidden)]
161pub use cluster::WorkerInvoker;
162#[cfg(any(test, feature = "cluster-unit-tests"))]
163#[doc(hidden)]
164pub use cluster::WorkerOperation;
165pub use cluster::{ConnectionMetadata, TestCluster, TestClusterConnection};
166#[doc(hidden)]
167pub use error::BootstrapResult;
168pub use error::PgEmbeddedError as Error;
169pub use error::{BootstrapError, BootstrapErrorKind, PgEmbeddedError, Result};
170#[cfg(feature = "privileged-tests")]
171#[cfg(all(
172 unix,
173 any(
174 target_os = "linux",
175 target_os = "android",
176 target_os = "freebsd",
177 target_os = "openbsd",
178 target_os = "dragonfly",
179 ),
180))]
181#[expect(
182 deprecated,
183 reason = "with_temp_euid() remains exported for backward compatibility whilst deprecated"
184)]
185pub use privileges::with_temp_euid;
186#[cfg(all(
187 unix,
188 any(
189 target_os = "linux",
190 target_os = "android",
191 target_os = "freebsd",
192 target_os = "openbsd",
193 target_os = "dragonfly",
194 ),
195))]
196pub use privileges::{default_paths_for, make_data_dir_private, make_dir_accessible, nobody_uid};
197
198use color_eyre::eyre::{Context, eyre};
199use ortho_config::OrthoConfig;
200use postgresql_embedded::{Settings, VersionReq};
201use serde::{Deserialize, Serialize};
202
203use crate::error::{ConfigError, ConfigResult};
204use camino::Utf8PathBuf;
205use std::ffi::OsString;
206
207#[derive(Debug, Clone, Serialize, Deserialize, OrthoConfig, Default)]
209#[ortho_config(prefix = "PG")]
210pub struct PgEnvCfg {
219 pub version_req: Option<String>,
221 pub port: Option<u16>,
223 pub superuser: Option<String>,
225 pub password: Option<String>,
227 pub data_dir: Option<Utf8PathBuf>,
229 pub runtime_dir: Option<Utf8PathBuf>,
231 pub locale: Option<String>,
233 pub encoding: Option<String>,
235}
236
237impl PgEnvCfg {
238 pub fn load() -> ConfigResult<Self> {
244 let args = [OsString::from("pg-embedded-setup-unpriv")];
245 Self::load_from_iter(args).map_err(|err| ConfigError::from(eyre!(err)))
246 }
247
248 pub fn to_settings(&self) -> Result<Settings> {
259 let mut s = Settings::default();
260
261 self.apply_version(&mut s)?;
262 self.apply_connection(&mut s);
263 self.apply_paths(&mut s);
264 self.apply_locale(&mut s);
265
266 Ok(s)
267 }
268
269 fn apply_version(&self, settings: &mut Settings) -> ConfigResult<()> {
270 if let Some(ref vr) = self.version_req {
271 settings.version =
272 VersionReq::parse(vr).context("PG_VERSION_REQ invalid semver spec")?;
273 }
274 Ok(())
275 }
276
277 fn apply_connection(&self, settings: &mut Settings) {
278 if let Some(p) = self.port {
279 settings.port = p;
280 }
281 if let Some(ref u) = self.superuser {
282 settings.username.clone_from(u);
283 }
284 if let Some(ref pw) = self.password {
285 settings.password.clone_from(pw);
286 }
287 }
288
289 fn apply_paths(&self, settings: &mut Settings) {
290 if let Some(ref dir) = self.data_dir {
291 settings.data_dir = dir.clone().into_std_path_buf();
292 }
293 if let Some(ref dir) = self.runtime_dir {
294 settings.installation_dir = dir.clone().into_std_path_buf();
295 }
296 }
297
298 fn apply_locale(&self, settings: &mut Settings) {
304 if let Some(ref loc) = self.locale {
305 settings.configuration.insert("locale".into(), loc.clone());
306 }
307 if let Some(ref enc) = self.encoding {
308 settings
309 .configuration
310 .insert("encoding".into(), enc.clone());
311 }
312 }
313}