diff options
| author | Doan Luu Duc Tai <doan@luuduct.ai> | 2026-10-30 22:36:11 -0500 |
|---|---|---|
| committer | Doan Luu Duc Tai <doan@luuduct.ai> | 2026-10-30 22:36:11 -0500 |
| commit | 6933ec28be3dac0dc17cd634c663db3b4818caad (patch) | |
| tree | 22690260136d598d2719d97deb169b847b31424c /src/project.rs | |
| parent | c3b361c45ac8dd898d5c83a161abed5fc138b6c0 (diff) | |
Centralize deployment settings in the root environment file
Document public domains, ports, upstreams, chat binding, and backup defaults alongside private settings. Validate shared configuration and render Caddy, cgit, and website links from an explicit public allowlist while preserving one installation environment file.
Diffstat (limited to 'src/project.rs')
| -rw-r--r-- | src/project.rs | 114 |
1 files changed, 39 insertions, 75 deletions
diff --git a/src/project.rs b/src/project.rs index 0dac2ef..6ef619a 100644 --- a/src/project.rs +++ b/src/project.rs @@ -16,7 +16,7 @@ use std::collections::BTreeMap; use std::env; use std::fs::{self, File, OpenOptions, Permissions}; -use std::io::Read; +use std::io::{Read, Write}; use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; use std::path::{Component, Path, PathBuf}; use std::process::Command; @@ -25,6 +25,7 @@ use std::time::Duration; use anyhow::{Context, Result, bail, ensure}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use stack_config::Config; use crate::{files, process}; @@ -116,11 +117,16 @@ impl Project { pub(crate) fn settings(&self) -> Result<Settings> { let path = self.root.join("var/state.json"); if path.exists() { - return Ok(serde_json::from_reader(File::open(path)?)?); + let mut settings: Settings = serde_json::from_reader(File::open(path)?)?; + let config = Config::load(&self.root)?; + if config.is_explicit("LOCAL_HTTP_PORT") { + settings.port = config.port("LOCAL_HTTP_PORT")?; + } + return Ok(settings); } Ok(Settings { mode: Mode::Local, - port: 8080, + port: Config::load(&self.root)?.port("LOCAL_HTTP_PORT")?, project: format!( "stack-{}", files::hash_bytes(self.root.as_os_str().as_encoded_bytes()) @@ -230,16 +236,17 @@ impl Project { pub(crate) fn initialize(&self, settings: &Settings) -> Result<()> { files::private_dir(&self.root.join("var/data"))?; let env_file = self.root.join(".env"); - if !env_file.exists() { + if !Config::load(&self.root)?.is_explicit("SEARXNG_SECRET") { let mut random = [0; 32]; File::open("/dev/urandom")?.read_exact(&mut random)?; - let secret = hex::encode(random); - files::write( - &env_file, - format!("SEARXNG_SECRET={secret}\n").as_bytes(), - 0o600, - )?; + let mut file = OpenOptions::new() + .create(true) + .append(true) + .mode(0o600) + .open(&env_file)?; + writeln!(file, "\nSEARXNG_SECRET={}", hex::encode(random))?; } + stack_config::initialize_public(&self.root)?; fs::set_permissions(&env_file, Permissions::from_mode(0o600))?; for name in ["repos", "caddy/data", "caddy/config", "searxng"] { fs::create_dir_all(self.root.join("var/data").join(name))?; @@ -268,42 +275,7 @@ impl Project { &self.root.join("infra/searxng"), &self.root.join("var/data/searxng"), )?; - if settings.mode == Mode::Local { - self.local_site(settings.port)?; - } - Ok(()) - } - - fn local_site(&self, port: u16) -> Result<()> { - let generated = self.root.join("var/local/web"); - if generated.exists() { - files::regular_tree(&generated)?; - fs::remove_dir_all(&generated)?; - } - files::copy_tree(&self.root.join("web"), &generated)?; - let index = generated.join("index.html"); - let html = fs::read_to_string(&index)? - .replace("https://git.doan.luuduct.ai", "/git") - .replace("https://search.doan.luuduct.ai", "/search") - .replace("https://openpgpkey.luuduct.ai", ""); - files::write(&index, html.as_bytes(), 0o644)?; - let template = self.root.join("infra/containers/cgit/cgitrc.local"); - let content = fs::read_to_string(&template)? - .lines() - .map(|line| { - if line.starts_with("clone-prefix=") { - format!("clone-prefix=http://localhost:{port}/git") - } else { - line.to_owned() - } - }) - .collect::<Vec<_>>() - .join("\n"); - files::write( - &self.root.join("var/local/cgitrc"), - format!("{content}\n").as_bytes(), - 0o644, - ) + crate::render::prepare(self, settings) } pub(crate) fn container_name(settings: &Settings, service: &str) -> String { @@ -343,9 +315,7 @@ impl Project { .args(["network", "create", &network]), )?; } - let environment: BTreeMap<String, String> = - dotenvy::from_path_iter(self.root.join(".env"))? - .collect::<std::result::Result<_, _>>()?; + let environment = Config::load(&self.root)?; for (name, service) in config .services .iter() @@ -364,7 +334,11 @@ impl Project { process::line(format_args!( "Running {}", if settings.mode == Mode::Production { - "https://doan.luuduct.ai".to_owned() + environment + .public_values(None)? + .get("SITE_ORIGIN") + .context("Missing public site origin")? + .to_owned() } else { format!("http://localhost:{}", settings.port) } @@ -401,7 +375,7 @@ impl Project { network: &str, name: &str, service: &Service, - environment: &BTreeMap<String, String>, + environment: &Config, ) -> Result<()> { let container = Self::container_name(settings, name); let mut command = self.command("podman")?; @@ -439,39 +413,29 @@ impl Project { ] .to_vec() } else { - ["[::]:80:80", "[::]:443:443", "[::]:443:443/udp"] - .map(str::to_owned) - .to_vec() + let http = environment.port("HTTP_PORT")?; + let https = environment.port("HTTPS_PORT")?; + vec![ + format!("[::]:{http}:{http}"), + format!("[::]:{https}:{https}"), + format!("[::]:{https}:{https}/udp"), + ] }; for port in ports { command.args(["--publish", &port]); } } for key in &service.environment { - command.arg("--env").arg(key).env( - key, - environment - .get(key) - .with_context(|| format!("Missing secret {key} in .env"))?, - ); + command + .arg("--env") + .arg(key) + .env(key, environment.value(key)?); } for mount in &service.mounts { let source = match mount.source.as_str() { - "@caddy" => self.root.join(if settings.mode == Mode::Local { - "infra/Caddyfile.local" - } else { - "infra/Caddyfile" - }), - "@web" => self.root.join(if settings.mode == Mode::Local { - "var/local/web" - } else { - "web" - }), - "@cgit" => self.root.join(if settings.mode == Mode::Local { - "var/local/cgitrc" - } else { - "infra/containers/cgit/cgitrc" - }), + "@caddy" => crate::render::directory(self, settings).join("Caddyfile"), + "@web" => crate::render::directory(self, settings).join("web"), + "@cgit" => crate::render::directory(self, settings).join("cgitrc"), other => self.path(other)?, }; let mode = if mount.read_only { "ro,z" } else { "rw,z" }; |
