diff options
Diffstat (limited to 'src/app/render.rs')
| -rw-r--r-- | src/app/render.rs | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/src/app/render.rs b/src/app/render.rs new file mode 100644 index 0000000..8a2d2ee --- /dev/null +++ b/src/app/render.rs @@ -0,0 +1,130 @@ +// Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::app::files; +use crate::app::project::{Mode, Project, Settings}; +use anyhow::Result; +use stack_config::Config; +use std::fs; +use std::path::PathBuf; + +pub(super) fn directory(project: &Project, settings: &Settings) -> PathBuf { + project.root.join(match settings.mode { + Mode::Local => "var/local", + Mode::Production => "var/production", + }) +} + +pub(super) fn prepare(project: &Project, settings: &Settings) -> Result<()> { + let config = Config::load(&project.root)?; + let local = (settings.mode == Mode::Local).then_some(settings.port); + let output = directory(project, settings); + let web = output.join("web"); + if web.exists() { + files::regular_tree(&web)?; + fs::remove_dir_all(&web)?; + } + files::copy_tree(&project.root.join("web"), &web)?; + let file = web.join("index.html"); + files::write( + &file, + config + .render(&fs::read_to_string(&file)?, local)? + .as_bytes(), + 0o644, + )?; + let template = project.root.join(if settings.mode == Mode::Local { + "infra/Caddyfile.local" + } else { + "infra/Caddyfile" + }); + files::write( + &output.join("Caddyfile"), + config + .render(&fs::read_to_string(template)?, local)? + .as_bytes(), + 0o644, + )?; + let cgit = fs::read_to_string(project.root.join("infra/containers/cgit/cgitrc"))?; + let mut rendered = config.render(&cgit, local)?; + if let Some(port) = local { + rendered = rendered.replace( + "clone-prefix=/git", + &format!("clone-prefix=http://localhost:{port}/git"), + ); + } + files::write(&output.join("cgitrc"), rendered.as_bytes(), 0o644) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn custom_domains_and_ports_reach_public_outputs_without_secrets() -> Result<()> { + let root = tempfile::tempdir()?; + for name in ["web", "infra"] { + files::copy_tree( + &PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(name), + &root.path().join(name), + )?; + } + fs::write( + root.path().join(".env"), + "SITE_HOST=home.example.net\nGIT_HOST=git.example.net\nHTTP_PORT=8181\nHTTPS_PORT=8443\nSEARXNG_SECRET=do-not-publish-this-sentinel\n", + )?; + let project = Project::at(root.path())?; + let mut settings = project.settings()?; + settings.mode = Mode::Production; + prepare(&project, &settings)?; + let output = directory(&project, &settings); + let caddy = fs::read_to_string(output.join("Caddyfile"))?; + anyhow::ensure!( + caddy.contains("https://home.example.net:8443"), + "Test invariant failed" + ); + anyhow::ensure!(caddy.contains("http_port 8181"), "Test invariant failed"); + anyhow::ensure!(!caddy.contains("do-not-publish"), "Test invariant failed"); + let homepage = fs::read_to_string(output.join("web/index.html"))?; + anyhow::ensure!( + homepage.contains("https://git.example.net:8443"), + "Test invariant failed" + ); + let stylesheet = files::hash_file(&root.path().join("web/styles.css"))?; + anyhow::ensure!( + homepage.contains(&format!("/styles.css?v={stylesheet}")), + "Test invariant failed" + ); + anyhow::ensure!( + homepage.contains("https://chat.doan.luuduct.ai:8443/LIMITATIONS.txt"), + "Test invariant failed" + ); + settings.mode = Mode::Local; + settings.port = 8180; + prepare(&project, &settings)?; + let preview = fs::read_to_string(directory(&project, &settings).join("web/index.html"))?; + anyhow::ensure!(preview.contains("href=\"/git/\""), "Test invariant failed"); + anyhow::ensure!( + preview.contains("http://chat.localhost:8180/LIMITATIONS.txt"), + "Test invariant failed" + ); + anyhow::ensure!( + fs::read_to_string(directory(&project, &settings).join("cgitrc"))? + .contains("clone-prefix=http://localhost:8180/git"), + "Test invariant failed" + ); + Ok(()) + } +} |
